{"openapi":"3.1.0","info":{"title":"x402 Payment Facilitator API","description":"\nUltravioleta DAO x402 Payment Facilitator - Gasless micropayments for the agentic economy.\n\n## Overview\n\nThe x402 facilitator enables gasless micropayments across multiple blockchain networks using the HTTP 402 Payment Required protocol. It acts as a settlement intermediary, verifying EIP-3009/EIP-712 payment authorizations and submitting them on-chain.\n\n## Supported Networks\n\n### EVM Chains (Mainnet)\nEthereum, Base, Polygon, Optimism, Avalanche, Arbitrum, Celo, HyperEVM, Unichain, Monad, Scroll, Robinhood Chain (settles Paxos USDG - no native USDC), BSC, SKALE Base\n\n### EVM Chains (Testnet)\nEthereum Sepolia, Base Sepolia, Polygon Amoy, Optimism Sepolia, Avalanche Fuji, Arbitrum Sepolia, Celo Sepolia, HyperEVM Testnet, Unichain Sepolia, SKALE Base Sepolia, Monad Testnet, Robinhood Chain Testnet\n\n### SVM Chains (Solana Virtual Machine)\n- **Solana**: Mainnet (`solana`) and Devnet (`solana-devnet`)\n- **Fogo**: Mainnet (`fogo`) and Testnet (`fogo-testnet`)\n- **XRPL (XRP Ledger)**: Mainnet (`xrpl`) and Testnet (`xrpl-testnet`) — native XRP, pre-signed Payment transaction blobs; not the EVM `xrpl-evm` sidechain\n\n### Other Non-EVM Chains\n- **NEAR Protocol**: Mainnet (`near`) and Testnet (`near-testnet`)\n- **Stellar/Soroban**: Mainnet (`stellar`) and Testnet (`stellar-testnet`)\n- **Algorand**: Mainnet (`algorand`) and Testnet (`algorand-testnet`)\n- **Sui**: Mainnet (`sui`) and Testnet (`sui-testnet`)\n\n## Core Endpoints\n\n- `POST /verify` - Verify payment authorization structure and signatures\n- `POST /settle` - Submit verified payment to blockchain for settlement\n- `GET /supported` - List all supported networks and payment schemes\n\n## ERC-8004 Reputation (Trustless Agents)\n\nThe facilitator supports [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) for AI agent identity and reputation across **21 networks** (12 mainnets + 9 testnets), spanning both EVM and Solana.\n\n**EVM networks:** `ethereum`, `base`, `polygon`, `arbitrum`, `optimism`, `celo`, `bsc`, `monad`, `avalanche`, `ethereum-sepolia`, `base-sepolia`, `polygon-amoy`, `arbitrum-sepolia`, `optimism-sepolia`, `celo-sepolia`, `avalanche-fuji`\n\n**Solana networks:** `solana`, `solana-devnet` (via [QuantuLabs 8004-solana](https://github.com/QuantuLabs/8004-solana) + [ATOM Engine](https://github.com/QuantuLabs/8004-atom))\n\n**Note:** For EVM networks, `agentId` is a numeric uint256 (e.g., `42`). For Solana, `agentId` is a base58 Pubkey (e.g., `7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv`). Solana reputation responses include bonus `atomStats` with trust tiers, quality scores, and anti-Sybil metrics.\n\n### Endpoints:\n- `POST /register` - Register a new agent on-chain (gasless; sync or async via `Prefer: respond-async`)\n- `GET /register/status/{job_id}` - Poll an async registration until `agentId` is ready\n- `POST /feedback` - Submit on-chain reputation feedback (EVM only)\n- `POST /feedback/revoke` - Revoke previously submitted feedback (EVM only). **Admin only**: requires `Authorization: Bearer <ERC8004_ADMIN_TOKEN>` and returns 404 when no token is configured\n- `POST /feedback/evm/prepare` - Build the digest the RATER signs, for an EIP-7702 relayed rating (EVM)\n- `POST /feedback/evm/submit` - Relay a rater-authored rating as a type-4 transaction, paying the gas (EVM)\n- `POST /feedback/solana/prepare` - Build a feedback transaction for the RATER to sign (Solana)\n- `POST /feedback/solana/submit` - Co-sign and send a rater-signed feedback transaction (Solana)\n- `POST /feedback/response` - Append agent response to feedback (EVM only)\n- `GET /reputation/:network/:agentId` - Query agent reputation summary (EVM + Solana)\n- `GET /identity/:network/:agentId` - Get agent identity from registry (EVM + Solana)\n- `GET /identity/:network/:agentId/metadata/:key` - Read specific agent metadata (EVM + Solana)\n- `GET /identity/:network/total-supply` - Get total registered agents on a network (EVM + Solana)\n\n## Bazaar Discovery\n\nCurated resource discovery for x402-enabled services. Entries carry a discovery `source`,\na liveness `health` status from periodic probing, and a curated `tier`\n(`first_party` > `vip` > `verified` > `listed`) which also drives listing order.\n\n- `GET /discovery/resources` - List curated resources (filters: category, provider, tag, network, source, sourceFacilitator, q, health, tier; any other parameter is a 400)\n- `GET /discovery/stats` - Aggregate catalog metrics (60s cache)\n- `GET /bazaar` - HTML Bazaar explorer UI\n- `GET /discovery/attestation/{hash}` - ERC-8004 attestation evidence body\n- `POST /discovery/register` - Register a new resource (rate limited)\n\n**Admin** (require `Authorization: Bearer <BAZAAR_ADMIN_TOKEN>`; return 404 when no admin token is configured):\n\n- `DELETE /discovery/resources?url=...` - Permanently unregister a resource\n- `POST /discovery/admin/suppress` - Hide a resource from listings without deleting it\n- `POST /discovery/admin/release` - Un-suppress a resource\n\n## Errors\n\nEvery refusal is JSON with `content-type: application/json`, including the ones\na framework normally answers with an empty body: a `405` for the wrong method,\na `404` for a path nothing serves, and the `429` from the rate limiter. The\nshape is:\n\n```json\n{\n  \"error\": \"human-readable, may change\",\n  \"code\": \"machine_readable_stable\",\n  \"hint\": \"what to do about it\"\n}\n```\n\nBranch on `code`, never on the prose in `error`. Codes in use include\n`invalid_request_body`, `method_not_allowed`, `not_found`, `not_acceptable`,\n`rate_limited` and `rate_limit_key_unavailable`; endpoint-specific codes are\ndocumented on the operations that return them.\n\n## Rate limits\n\nLimits are per client IP and are reported on every rate-limited response, not\njust on the refusal:\n\n| Header | Present on | Meaning |\n|---|---|---|\n| `x-ratelimit-limit` | `200` and `429` | burst size of the bucket this route draws on |\n| `x-ratelimit-remaining` | `200` and `429` | tokens left in that bucket |\n| `retry-after` | `429` | seconds to wait before retrying |\n| `x-ratelimit-after` | `429` | the same value under tower_governor's own name |\n\nRead `x-ratelimit-remaining` and slow down before it reaches zero. The buckets\nrefill one token every N seconds rather than granting N per minute, so the\nsustained rate and the burst are different numbers. Surfaces have separate\nbuckets, with one deliberate exception: `POST /mcp` shares the `/verify` and\n`/settle` bucket, because an `x402_settle` tool call costs the chain exactly\nwhat `POST /settle` does. Free static routes (`/health`, `/supported`, the\ndiscovery documents) carry no limit and therefore no headers.\n\n## Content negotiation\n\n`GET /` answers `text/html` by default and `text/markdown` -- the bytes of\n`/index.md` -- to a request whose `Accept` prefers it. `/llms.txt` relabels its\nown bytes the same way. Those responses carry `Vary: Accept, Accept-Encoding`.\nNegotiation follows RFC 9110 12.5.1: ranked by `q`, ties broken by specificity,\n`q=0` honoured as a refusal, and a missing `Accept` or `*/*` treated as no\nconstraint rather than as grounds for a `406`.\n\n## Protocol Documentation\n\n- [x402 Protocol](https://x402.org)\n- [EIP-3009 (transferWithAuthorization)](https://eips.ethereum.org/EIPS/eip-3009)\n- [ERC-8004 (Trustless Agents)](https://eips.ethereum.org/EIPS/eip-8004)\n- [Ultravioleta DAO](https://ultravioletadao.xyz)\n","contact":{"name":"Ultravioleta DAO","url":"https://ultravioletadao.xyz"},"license":{"name":"Apache-2.0","url":"https://www.apache.org/licenses/LICENSE-2.0"},"version":"2.16.0"},"servers":[{"url":"https://facilitator.ultravioletadao.xyz","description":"Production"},{"url":"http://localhost:8080","description":"Local Development"}],"paths":{"/.well-known/agent-card.json":{"get":{"tags":["Agentic"],"summary":"A2A agent card","description":"The agent-to-agent card: identity, transport, capabilities and skills (verify, settle, supported, accepts).","operationId":"path_agent_card","responses":{"200":{"description":"A2A agent card","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/agent-skills/index.json":{"get":{"tags":["Agentic"],"summary":"Agent skills index","description":"One entry, pointing at `/skill.md` with its real sha256 digest. A test fails the build if the digest stops matching the file.","operationId":"path_agent_skills_index","responses":{"200":{"description":"Skills index","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/agent.json":{"get":{"tags":["Agentic"],"summary":"A2A agent card (legacy path)","description":"Byte-identical to `/.well-known/agent-card.json`. Several clients still look here first, and two cards that could disagree is worse than one served twice.","operationId":"path_agent_json_legacy","responses":{"200":{"description":"A2A agent card","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/api-catalog":{"get":{"tags":["Agentic"],"summary":"API catalog (RFC 9727)","description":"The linkset: service-desc to the OpenAPI document, service-doc to the Swagger UI, service-meta to the agent manual, status to the health endpoint.","operationId":"path_api_catalog","responses":{"200":{"description":"RFC 9727 linkset","content":{"application/linkset+json":{"schema":{"type":"object"}}}}}}},"/.well-known/ard.json":{"get":{"tags":["Agentic"],"summary":"Agentic Resource Discovery catalog","description":"The ARD v0.91 catalog (<https://agenticresourcediscovery.org/spec>): one document naming every agentic resource this host offers -- the MCP server card, the A2A card, the skill, the OpenAPI and the llms.txt index. Each entry carries a domain-anchored `urn:air:` identifier, a media type, a `url`, and the representative queries a registry indexes on. The predecessor path `/.well-known/ai-catalog.json` is not served: the spec makes consulting it optional for consumers and tells publishers to move here.","operationId":"path_ard_json","responses":{"200":{"description":"ARD manifest: an object with an `entries` array","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/mcp/server-card.json":{"get":{"tags":["Agentic"],"summary":"MCP server card","description":"Where the MCP server lives and what it can do: `transport.endpoint` is `POST /mcp`, and the `tools` array is the same four names `tools/list` returns. `serverInfo.version` is stamped at runtime from the running release, so it cannot go stale.","operationId":"path_mcp_server_card","responses":{"200":{"description":"MCP server card","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["Agentic"],"summary":"Protected resource metadata (RFC 9728)","description":"Published with an empty `authorization_servers` on purpose: it exists so an OAuth-capable agent can discover in one GET that this resource is NOT OAuth-protected, instead of trying and failing.","operationId":"path_oauth_protected_resource","responses":{"200":{"description":"RFC 9728 metadata","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/x402":{"get":{"tags":["Agentic"],"summary":"x402 discovery document","description":"Declares `role: \"facilitator\"`, the verify/settle/supported endpoints, the five schemes and the networks. `paidRoutes` is empty because this service charges nothing and none of its routes answer 402. Token contract addresses are deliberately absent so the document cannot drift from the code; read them from `POST /accepts`.","operationId":"path_x402_discovery","responses":{"200":{"description":"x402 discovery document","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/accepts":{"post":{"tags":["Core"],"summary":"Negotiate payment requirements (Faremeter middleware)","description":"\nNegotiation endpoint used by `@faremeter/middleware` and compatible x402 clients.\n\nReceives merchant payment requirements, matches them against the facilitator's\nsupported capabilities, and returns enriched requirements with facilitator-specific\ndata (feePayer, tokens, escrow contracts, etc.).\n\n**How it works:**\n1. Middleware sends the merchant's desired payment requirements\n2. Facilitator filters to only those it can handle (matching scheme + network)\n3. Facilitator enriches each match with `extra` data (feePayer, tokens, features)\n4. Middleware uses the enriched requirements in the 402 response to clients\n\n**Supports both v1 and v2 network formats** (auto-detected):\n- v1: `\"network\": \"base\"` (string enum)\n- v2: `\"network\": \"eip155:8453\"` (CAIP-2 format)\n\n**Request body:**\n```json\n{\n  \"x402Version\": 1,\n  \"accepts\": [\n    {\n      \"scheme\": \"exact\",\n      \"network\": \"solana\",\n      \"maxAmountRequired\": \"10000\",\n      \"asset\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n      \"payTo\": \"SomePublicKey...\",\n      \"description\": \"Access to premium API\",\n      \"maxTimeoutSeconds\": 90,\n      \"resource\": \"https://api.example.com/data\"\n    }\n  ],\n  \"error\": \"\"\n}\n```\n\n**Response (enriched):**\n```json\n{\n  \"x402Version\": 1,\n  \"accepts\": [\n    {\n      \"scheme\": \"exact\",\n      \"network\": \"solana\",\n      \"maxAmountRequired\": \"10000\",\n      \"asset\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n      \"payTo\": \"SomePublicKey...\",\n      \"description\": \"Access to premium API\",\n      \"maxTimeoutSeconds\": 90,\n      \"resource\": \"https://api.example.com/data\",\n      \"extra\": {\n        \"feePayer\": \"F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq\",\n        \"tokens\": [\n          { \"token\": \"usdc\", \"address\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\", \"decimals\": 6 }\n        ]\n      }\n    }\n  ],\n  \"error\": \"\"\n}\n```\n\n**Requirements this facilitator cannot serve are reported, not dropped.**\n\nEvery requirement that does not survive the match comes back in `rejected`, an\narray that is ALWAYS present -- empty when everything matched. Each entry carries\nthe offending `index`, the `scheme` and `network` as you sent them, a `reason`\nfrom a bounded vocabulary, and an advisory `detail`.\n\n| `reason` | What it means | What to do |\n|---|---|---|\n| `malformed` | `scheme` or `network` missing, or not a string | Fix the requirement |\n| `network_unknown` | That string names no chain, in either spelling | Read `GET /supported` |\n| `scheme_unknown` | Not an x402 scheme this facilitator implements | Read `GET /supported` |\n| `network_unsupported` | A known chain this deployment does not serve | Offer another chain |\n| `scheme_unsupported_on_network` | Both known, but not that pair | `detail` names what IS served there |\n\n`reason` is a closed set -- switch on it. `detail` is prose and may change.\n\nNote the status code does not move: a negotiation in which nothing matched is\nstill `HTTP 200`, because the request was well formed. Branch on\n`accepts.length` and `rejected`, not on the status.\n","operationId":"path_accepts_post","requestBody":{"description":"Merchant payment requirements to negotiate","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Enriched payment requirements","content":{"application/json":{"schema":{"type":"object"},"example":{"x402Version":1,"accepts":[{"scheme":"exact","network":"base","maxAmountRequired":"1000000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","payTo":"0x...","extra":{"tokens":[{"token":"usdc","address":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","decimals":6}]}}],"rejected":[{"index":1,"scheme":"exact","network":"cosmos:hub-4","reason":"network_unknown","detail":"`cosmos:hub-4` names no chain this facilitator knows, under either the x402 v1 name (\"base\") or the CAIP-2 identifier (\"eip155:8453\"). GET /supported lists every network served, in both spellings"}],"error":""}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object"},"example":{"x402Version":1,"accepts":[],"error":"Missing or invalid 'accepts' array"}}}}}}},"/api/stats":{"get":{"tags":["Discovery"],"summary":"Pre-aggregated totals per network and asset","description":"Totals maintained on write, so the cost of this call does not grow with history — it never scans.\n\nTwo caveats travel in the response body because they change how the numbers should be read. **Operations that ERROR are not recorded at all**, so a 100% success rate means 'no failures were recorded', not 'no failures occurred'. And **counting began when the store was enabled**, so anything settled before that is unknown rather than zero.\n\n`volumeAtomic` is a STRING: these are u256-shaped values and a JSON number silently loses precision above 2^53.","operationId":"path_api_stats","responses":{"200":{"description":"Aggregated totals","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Store unavailable or not configured"}}}},"/auth.md":{"get":{"tags":["Agentic"],"summary":"Authentication guide","description":"How to authenticate against this facilitator, which is: you do not. No accounts, no API keys, no OAuth. Documents the per-IP rate limits and the operator-only admin routes instead.","operationId":"path_auth_md","responses":{"200":{"description":"Markdown auth guide","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/bazaar":{"get":{"tags":["Bazaar"],"summary":"Bazaar explorer UI","description":"\nServes the HTML Bazaar explorer: a browsable view of the curated catalog backed by\n`GET /discovery/resources` and `GET /discovery/stats`.\n\nReturns `text/html`, not JSON. Use the discovery endpoints for programmatic access.\n","operationId":"path_bazaar_ui","responses":{"200":{"description":"Bazaar explorer HTML page","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/blacklist":{"get":{"tags":["Compliance"],"summary":"Get OFAC sanctioned addresses","description":"\nReturns the list of OFAC sanctioned blockchain addresses. Payments involving these addresses are blocked.\n\n**Response:**\n```json\n{\n  \"addresses\": [\"0x...\", \"0x...\"],\n  \"lastUpdated\": \"2026-01-15T00:00:00Z\",\n  \"source\": \"OFAC SDN List\"\n}\n```\n","operationId":"path_blacklist","responses":{"200":{"description":"Sanctioned addresses list","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/discovery/admin/release":{"post":{"tags":["Bazaar"],"summary":"Release a suppressed resource (admin)","description":"\n**Admin only.** Reverses `POST /discovery/admin/suppress`, making the resource visible in\nlistings again.\n\nRequires an `Authorization: Bearer <BAZAAR_ADMIN_TOKEN>` header. When the server has no admin\ntoken configured the whole admin surface is absent and this route returns **404**.\n\nRate limited to roughly 5 requests per minute per IP.\n\n**Request body:**\n```json\n{ \"url\": \"https://api.example.com/paid\" }\n```\n\n**Response:**\n```json\n{ \"success\": true, \"url\": \"https://api.example.com/paid\", \"suppressed\": false }\n```\n","operationId":"path_bazaar_admin_release","parameters":[{"name":"Authorization","in":"header","description":"Bearer <BAZAAR_ADMIN_TOKEN>","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Release request: url of the suppressed resource","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Resource released","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"url":"https://api.example.com/paid","suppressed":false}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Admin surface disabled (no admin token configured)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/discovery/admin/suppress":{"post":{"tags":["Bazaar"],"summary":"Suppress a resource (admin)","description":"\n**Admin only.** Hides a resource from every listing without deleting it, so the entry can be\nreleased later.\n\nRequires an `Authorization: Bearer <BAZAAR_ADMIN_TOKEN>` header. When the server has no admin\ntoken configured the whole admin surface is absent and this route returns **404**.\n\nRate limited to roughly 5 requests per minute per IP.\n\n**Request body:**\n```json\n{ \"url\": \"https://api.example.com/paid\", \"reason\": \"spam\" }\n```\n\n**Response:**\n```json\n{ \"success\": true, \"url\": \"https://api.example.com/paid\", \"suppressed\": true }\n```\n","operationId":"path_bazaar_admin_suppress","parameters":[{"name":"Authorization","in":"header","description":"Bearer <BAZAAR_ADMIN_TOKEN>","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Suppression request: url plus a reason string","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Resource suppressed","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"url":"https://api.example.com/paid","suppressed":true}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Admin surface disabled (no admin token configured)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/discovery/attestation/{hash}":{"get":{"tags":["Bazaar"],"summary":"Get attestation evidence","description":"\nServes the hosted ERC-8004 attestation evidence body referenced by an on-chain curation\nattestation. The path key is a lowercase sha256 hex digest of the attested resource URL:\nexactly 64 characters matching `[0-9a-f]{64}`. Any other shape is rejected with 400 so a URL\npath segment can never be mapped to arbitrary content.\n\n**Response (application/json):**\n```json\n{\n  \"type\": \"uptime\",\n  \"endpoint\": \"https://mcp.execution.market/mcp\",\n  \"network\": \"base\",\n  \"agentId\": 2106,\n  \"uptime\": 99.77,\n  \"window\": { \"probes\": 100, \"ok\": 99 },\n  \"prober\": \"uvd-bazaar-health/1.0\"\n}\n```\n","operationId":"path_bazaar_attestation","parameters":[{"name":"hash","in":"path","description":"Lowercase sha256 hex digest of the resource URL (64 chars, [0-9a-f]{64})","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attestation evidence body","content":{"application/json":{"schema":{"type":"object"},"example":{"type":"uptime","endpoint":"https://mcp.execution.market/mcp","network":"base","agentId":2106,"uptime":99.77,"window":{"probes":100,"ok":99},"prober":"uvd-bazaar-health/1.0"}}}},"400":{"description":"Invalid evidence key format (not a 64-char lowercase hex digest)","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"No evidence stored for that key","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/discovery/register":{"post":{"tags":["Bazaar"],"summary":"Register a resource","description":"\nRegisters a paid resource in the Bazaar catalog so clients can discover it via\n`GET /discovery/resources`.\n\n**Rate limited** to roughly 5 requests per minute per IP: registration triggers DNS lookups and\noutbound fetches against caller-supplied URLs.\n\n**Request body:**\n```json\n{\n  \"url\": \"https://api.example.com/paid\",\n  \"type\": \"http\",\n  \"description\": \"Premium market data API\",\n  \"accepts\": [\n    {\n      \"scheme\": \"exact\",\n      \"network\": \"eip155:8453\",\n      \"asset\": \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n      \"amount\": \"10000\",\n      \"payTo\": \"0x...\",\n      \"maxTimeoutSeconds\": 300\n    }\n  ],\n  \"metadata\": {\n    \"provider\": \"Example\",\n    \"category\": \"data\",\n    \"tags\": [\"api\"]\n  }\n}\n```\n\n**Fields:**\n- `type` (required): one of `http`, `mcp`, `a2a`, `facilitator`.\n- `accepts` (required, except for `facilitator` entries): payment options in x402 v2 shape with\n  CAIP-2 `network` values.\n- `metadata` (optional): `provider`, `category`, `tags`.\n\n**Validation (400):** unsupported `scheme`, userinfo embedded in the URL\n(`https://user:pass@host`), a host resolving to a private / loopback / link-local / cloud metadata\nIP, or an empty `accepts` array on a non-`facilitator` type.\n","operationId":"path_bazaar_register","requestBody":{"description":"Resource registration request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"201":{"description":"Resource registered","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"message":"Resource registered successfully","url":"https://api.example.com/paid"}}}},"400":{"description":"Validation failure (bad scheme, URL userinfo, private/metadata IP host, empty accepts)","content":{"application/json":{"schema":{"type":"object"}}}},"409":{"description":"Resource already registered","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Rate limited (roughly 5 requests per minute per IP)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/discovery/resources":{"get":{"tags":["Bazaar"],"summary":"List curated bazaar resources","description":"\nLists x402-enabled resources known to the curated Bazaar catalog.\n\n**Ordering:** results are sorted by curated tier first (`first_party` > `vip` > `verified` > `listed`),\nthen by liveness (`alive` resources first), then by `lastUpdated` descending.\n\n**Health visibility:** when `health` is omitted, quarantined resources are hidden.\nPass `health=any` to return everything, or a specific status to filter to it.\n\n**Response:**\n```json\n{\n  \"x402Version\": 2,\n  \"items\": [\n    {\n      \"url\": \"https://api.meshrelay.xyz/payments/access/alpha-test\",\n      \"type\": \"http\",\n      \"x402Version\": 2,\n      \"description\": \"MeshRelay premium IRC channel access\",\n      \"accepts\": [\n        {\n          \"scheme\": \"exact\",\n          \"network\": \"eip155:8453\",\n          \"asset\": \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n          \"amount\": \"100000\",\n          \"payTo\": \"0xe4dc963c56979E0260fc146b87eE24F18220e545\",\n          \"maxTimeoutSeconds\": 300\n        }\n      ],\n      \"lastUpdated\": 1784818083,\n      \"metadata\": {\n        \"provider\": \"MeshRelay\",\n        \"category\": \"communication\",\n        \"tags\": [\"irc\"]\n      },\n      \"source\": \"self_registered\",\n      \"sourceFacilitator\": null,\n      \"firstSeen\": 1784818112,\n      \"health\": {\n        \"status\": \"alive\",\n        \"lastChecked\": 1784900000,\n        \"httpStatus\": 402,\n        \"latencyMs\": 240\n      },\n      \"curation\": {\n        \"tier\": \"first_party\",\n        \"label\": \"MeshRelay\",\n        \"firstParty\": true,\n        \"verification\": {\n          \"protocol\": \"erc8004\",\n          \"network\": \"base\",\n          \"agentId\": 2106,\n          \"feedbackCount\": 0,\n          \"uptime\": 99.77\n        }\n      }\n    }\n  ],\n  \"pagination\": { \"limit\": 10, \"offset\": 0, \"total\": 21195 }\n}\n```\n\n**Optional fields:** `metadata`, `sourceFacilitator`, `firstSeen`, `health` and `curation` are\nomitted when unknown. Only `url`, `type`, `x402Version`, `accepts`, `lastUpdated` and `source`\nare always present.\n\n**Timestamps** (`firstSeen`, `lastUpdated`, `health.lastChecked`) are Unix epoch **seconds**,\nserialized as JSON numbers -- not ISO-8601 strings and not milliseconds.\n\n**Unknown parameters are rejected with a 400**, listing the ones supported. A parameter the\nserver accepted and ignored would be indistinguishable from a filter that matched everything,\nso `?search=logs` fails loudly and points at `q` instead of quietly returning the whole catalog.\n","operationId":"path_bazaar_list","parameters":[{"name":"limit","in":"query","description":"Maximum number of resources to return (default: 10, max: 100)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Number of resources to skip (default: 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"category","in":"query","description":"Filter by metadata category (e.g., finance, communication)","required":false,"schema":{"type":"string"}},{"name":"provider","in":"query","description":"Filter by metadata provider name","required":false,"schema":{"type":"string"}},{"name":"tag","in":"query","description":"Filter by metadata tag","required":false,"schema":{"type":"string"}},{"name":"network","in":"query","description":"Exact CAIP-2 network match (e.g., eip155:8453, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","description":"Discovery source: self_registered | settlement | crawled | aggregated","required":false,"schema":{"type":"string"}},{"name":"sourceFacilitator","in":"query","description":"Facilitator the entry was aggregated from (e.g., coinbase, payai, thirdweb)","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search over url, description, provider and tags. Max 128 characters (longer returns 400)","required":false,"schema":{"type":"string"}},{"name":"health","in":"query","description":"Liveness filter: alive | degraded | auth_gated | quarantined | unknown | unprobeable | any. When omitted, quarantined resources are hidden; 'any' returns everything","required":false,"schema":{"type":"string"}},{"name":"tier","in":"query","description":"Curated tier filter: first_party | vip | verified | listed","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Curated resource listing","content":{"application/json":{"schema":{"type":"object"},"example":{"x402Version":2,"items":[{"url":"https://api.meshrelay.xyz/payments/access/alpha-test","type":"http","x402Version":2,"description":"MeshRelay premium IRC channel access","accepts":[{"scheme":"exact","network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","amount":"100000","payTo":"0xe4dc963c56979E0260fc146b87eE24F18220e545","maxTimeoutSeconds":300}],"lastUpdated":1784818083,"metadata":{"provider":"MeshRelay","category":"communication","tags":["irc"]},"source":"self_registered","sourceFacilitator":null,"firstSeen":1784818112,"health":{"status":"alive","lastChecked":1784900000,"httpStatus":402,"latencyMs":240},"curation":{"tier":"first_party","label":"MeshRelay","firstParty":true,"verification":{"protocol":"erc8004","network":"base","agentId":2106,"feedbackCount":0,"uptime":99.77}}}],"pagination":{"limit":10,"offset":0,"total":21195}}}}},"400":{"description":"Invalid query: an unsupported parameter, or `q` longer than 128 characters","content":{"application/json":{"schema":{"type":"object"},"example":{"error":"unknown query parameter: search","hint":"did you mean q?","supported":["limit","offset","category","network","provider","tag","source","sourceFacilitator","health","tier","q"]}}}}}},"delete":{"tags":["Bazaar"],"summary":"Unregister a resource (admin)","description":"\n**Admin only.** Permanently removes a resource from the Bazaar catalog.\n\nRequires an `Authorization: Bearer <BAZAAR_ADMIN_TOKEN>` header. When the server has no admin\ntoken configured the whole admin surface is absent and this route returns **404**.\n\nRate limited to roughly 5 requests per minute per IP.\n\n**Response:**\n```json\n{ \"success\": true, \"url\": \"https://api.example.com/paid\", \"removed\": true }\n```\n","operationId":"path_bazaar_admin_delete","parameters":[{"name":"url","in":"query","description":"Exact resource URL to unregister","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Bearer <BAZAAR_ADMIN_TOKEN>","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resource removed","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"url":"https://api.example.com/paid","removed":true}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Unknown URL, or admin surface disabled (no admin token configured)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/discovery/stats":{"get":{"tags":["Bazaar"],"summary":"Bazaar catalog statistics","description":"\nAggregate metrics for the curated Bazaar catalog. Served from a 60-second in-process cache,\nso counters can lag recent registrations or health probes by up to a minute.\n\n- `total` counts every resource in the catalog.\n- `visible` counts the resources returned by the default `GET /discovery/resources` listing\n  (quarantined resources excluded).\n\n**Response:**\n```json\n{\n  \"total\": 21195,\n  \"visible\": 19263,\n  \"bySource\": { \"aggregated\": 21067, \"self_registered\": 128 },\n  \"bySourceFacilitator\": { \"payai\": 19800, \"thirdweb\": 622, \"coinbase\": 336 },\n  \"byNetwork\": { \"eip155:8453\": 20991, \"eip155:1\": 56 },\n  \"byTier\": { \"first_party\": 10, \"vip\": 127, \"verified\": 1814, \"listed\": 19244 },\n  \"byHealth\": { \"alive\": 1814, \"quarantined\": 1932, \"auth_gated\": 263, \"unknown\": 17029 },\n  \"generatedAt\": 1784900000\n}\n```\n","operationId":"path_bazaar_stats","responses":{"200":{"description":"Catalog metrics","content":{"application/json":{"schema":{"type":"object"},"example":{"total":21195,"visible":19263,"bySource":{"aggregated":21067,"self_registered":128},"bySourceFacilitator":{"payai":19800,"thirdweb":622,"coinbase":336},"byNetwork":{"eip155:8453":20991,"eip155:1":56},"byTier":{"first_party":10,"vip":127,"verified":1814,"listed":19244},"byHealth":{"alive":1814,"quarantined":1932,"auth_gated":263,"unknown":17029},"generatedAt":1784900000}}}}}}},"/dx402/anchor":{"post":{"tags":["DX402"],"summary":"Register sealed evidence for a settled payment","description":"A resource server reports that it sealed a response body and wrote the ciphertext somewhere durable. The facilitator notarises the claim with an EIP-712 `EvidenceReceipt` and indexes it.\n\n**This request carries metadata only.** The plaintext never reaches the facilitator, and in `direct` mode neither does anything that could decrypt it — the content key is wrapped to the payer's own public key, recovered from the payment signature. A leak of this facilitator's storage reveals pointers and hashes, never payloads.\n\n**Every anchor is judged against the chain.** Send `proofOfPayment` (from `/settle`) and `sellerSignature` (EIP-712 `DX402 Anchor` by the payee; raw ed25519 for Solana/Stellar payees). The record carries `verified` (the chain confirmed the payee and the payer) and `signed` (the signature matched the payee you declared — a diagnostic, not proof). Authority is a ladder: provisional < signed < verified; a weaker claim never locks out a stronger one, and only `verified` is final. `DX402_REQUIRE_PROOF=false` (phase 1, default) records failing anchors as provisional with `notVerifiedReason`; phase 2 rejects them with 402.\n\n**Payments released from an x402r escrow** must add `escrowRelease` (the escrow authorization plus the funder): on that rail the ERC-20 `from` is the operator's TokenStore, so the facilitator asks the escrow to `getHash` the authorization and requires a `paymentInfoHash` this very transaction captured. A transaction that settled more than one escrow payment is refused (`dx402_escrow_release_ambiguous`). The authorization's `token` must be the one the verified `Transfer` was emitted by and its `operator` must be a PaymentOperator this facilitator knows for the network (`dx402_escrow_operator_unknown`); and on every rail the proof's `token` must be in the facilitator's own deployment table for that network (`proof_token_not_allowed`) -- a `Transfer` from an unknown contract proves nothing about who paid.\n\nAvailable only when `ENABLE_DX402=true`; otherwise the route does not exist.","operationId":"path_dx402_anchor","responses":{"201":{"description":"Evidence recorded; body carries the signed receipt plus `verified`/`signed`/`notVerifiedReason`","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"`dx402_proof_rejected` — phase 2 only; the verdict names the rung that failed"},"409":{"description":"`dx402_already_anchored` — an existing record outranks this one"},"413":{"description":"Body over 64 KiB — emitted by the request-body limit layer, not by DX402"},"422":{"description":"`dx402_signature_not_verified` (a signature that could not supersede) or `dx402_backend_unavailable` (a backend this deployment does not serve)"},"503":{"description":"Store or index unavailable — RETRYABLE, do not record as 'no evidence'"}}}},"/dx402/blob/{paymentId}":{"get":{"tags":["DX402"],"summary":"The sealed ciphertext for a payment","description":"Streams the sealed evidence blob. **Unauthenticated on purpose.**\n\nIn `direct` mode the bytes are sealed to the payer's own public key, so handing them to anyone who asks reveals nothing — the access control lives in the cryptography rather than in an ACL that could be misconfigured. This is also why the evidence bucket is private and never exposed: this route is the only way in, and it can only ever serve ciphertext.\n\nA DX402 pointer (`s3+https://…/dx402/blob/{paymentId}`) resolves here. Pointers address the *payment*, not the storage layout, so one a buyer is holding a year from now keeps working even if the backing keys are reorganised.\n\nResponses are cacheable for the same reason they are public: an intermediary that caches this stores something unreadable. Attack III of *Five Attacks on x402* measured 100% leakage of paid responses through an nginx cache; DX402 makes that leak worthless.","operationId":"path_dx402_blob","parameters":[{"name":"paymentId","in":"path","description":"Payment identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sealed ciphertext (application/octet-stream)","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"No evidence recorded"},"410":{"description":"Past the retention window"},"503":{"description":"Store unavailable — RETRYABLE"}}}},"/dx402/evidence/{paymentId}":{"get":{"tags":["DX402"],"summary":"Look up the evidence anchored for a payment","description":"Returns the pointer, the plaintext content hash, the mode, the signed receipt, and the trust level: `verified` (the chain confirmed authorship), `signed` (the declared payee signed), and `notVerifiedReason` when it is not verified. An absent `verified` reads as not proven — a pointer alone is a claim, not evidence.\n\n`contentHash` is over the **plaintext**, deliberately. Hashing the ciphertext would only prove the blob was not corrupted in storage; hashing the plaintext lets a buyer prove the anchored blob decrypts to exactly the bytes they were served — the check that catches a seller anchoring something other than what it delivered.\n\n**404 and 410 are different answers.** 404 means no evidence was ever recorded; 410 means the retention window lapsed. In a dispute those are not interchangeable.","operationId":"path_dx402_evidence","parameters":[{"name":"paymentId","in":"path","description":"keccak256(caip2Network || txHash), or the `payment-identifier` value","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Evidence record","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No evidence recorded for this payment"},"410":{"description":"Past the retention window"},"503":{"description":"Index unavailable — RETRYABLE"}}}},"/dx402/receipt/{paymentId}":{"get":{"tags":["DX402"],"summary":"The signed evidence receipt, verifiable offline","description":"The EIP-712 receipt alone, plus the domain and the signer address.\n\nAnyone can verify this without calling the facilitator again — which is precisely the property the IETF x402 receipt drafts identify as missing from a bare `PAYMENT-RESPONSE`, where an auditor cannot validate a retained receipt without contacting the facilitator.\n\nDomain: `{name: \"DX402 Evidence\", version: \"1\", chainId}`. The receipt records `mode`, because a `direct` receipt and an `escrowed` receipt make materially different claims about who can read the payload.","operationId":"path_dx402_receipt","parameters":[{"name":"paymentId","in":"path","description":"Payment identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Signed receipt with its domain and signer","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No evidence recorded"},"410":{"description":"Past the retention window"}}}},"/dx402/recover":{"post":{"tags":["DX402"],"summary":"Release a wrapped content key (escrowed mode)","description":"**Returns 501 — `escrowed` recovery is out of scope in spec v0.2 (§17).**\n\n`direct` mode — the default and the whole point of DX402 — needs no recovery endpoint at all: the buyer already holds the only key that opens the payload, so retrieval is arithmetic rather than an authorization decision anyone could refuse or misconfigure.\n\nThis returns an honest 501 rather than a stub that appears to work, so no integrator builds an escrowed flow against a signature check that does not exist yet.","operationId":"path_dx402_recover","responses":{"501":{"description":"Escrowed recovery is not served; a declared read key is the recommended pattern for custody"}}}},"/dx402/repair/{paymentId}":{"post":{"tags":["DX402"],"summary":"Audit one anchor, and optionally correct a pointer that names nothing","description":"**Admin only.** `Authorization: Bearer <DX402_ADMIN_TOKEN>`, and **404 when no token is configured** — fail-closed, so the route is indistinguishable from absent. Its own token, deliberately not shared with the bazaar or ERC-8004 admin surfaces: this one re-signs a facilitator attestation.\n\nExists for the anchors written while the evidence pointer was a *prediction* nobody reconciled. On the `ipfs` backend a Pinata failure put the bytes in the S3 fallback while the record — and the signed receipt — went on naming an IPFS object that never existed. Reading it fails silently: the fallback store treats the primary's `NotFound` as a verdict and never retries, so the anchor returned 201, the receipt carries our signature, and the evidence is unreachable with no error anywhere.\n\n`write` defaults to **false**. An audit reports `repairable` and changes nothing; only `?write=true` rewrites. Auditing is safe and rewriting a signed attestation is not, so the dangerous half has to be asked for by name — otherwise the safe-looking call would be the dangerous one.\n\nA repair re-signs, because `pointer` is part of the EIP-712 type hash and a corrected pointer under the old signature is a receipt that does not verify. That is why this lives here rather than in a script: the signing key must not leave the service. It can only ever change *where the bytes are* — `verified` and `signed` are carried structurally from the record it read, so a repair cannot escalate authority.\n\n`lost` is never papered over. A record pointing at a real absence is telling the truth, and rewriting it would only hide that the evidence is gone.","operationId":"path_dx402_repair","parameters":[{"name":"paymentId","in":"path","description":"keccak256(caip2Network || txHash)","required":true,"schema":{"type":"string"}},{"name":"write","in":"query","description":"false (default) audits; true rewrites the record and re-signs","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Verdict: healthy | repairable | repaired | lost"},"401":{"description":"Missing or invalid admin credentials"},"404":{"description":"No admin token configured, or the payment has no evidence"},"409":{"description":"The row changed between the audit and the write; nothing was touched"}}}},"/dx402/stats":{"get":{"tags":["DX402"],"summary":"How much evidence this facilitator has notarised","description":"Anchor count, the default backend and retention, the address that signs receipts, and `backends[]` — every store this deployment can name, each with `retention`, `revocable`, `public`, `enabled` and a `disabledReason`. Derived from configuration, so it cannot promise a backend that has never held a byte; clients and the landing page read this instead of a hardcoded list.\n\n`anchored` is a **floor**, not a ledger. Evidence whose index write failed is real and is not counted here, in the same way `/api/stats` undercounts operations.","operationId":"path_dx402_stats","responses":{"200":{"description":"DX402 status and counters","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/escrow/state":{"post":{"tags":["Escrow"],"summary":"Query escrow payment state","description":"\nQueries the on-chain state of an escrow payment from the AuthCaptureEscrow contract.\nThis is a read-only view call (no gas consumed).\n\nReturns the capturable amount, refundable amount, and whether payment has been fully collected.\n\n**Request body:**\n```json\n{\n  \"paymentInfo\": {\n    \"operator\": \"0x...\",\n    \"receiver\": \"0x...\",\n    \"token\": \"0x...\",\n    \"maxAmount\": \"1000000\",\n    \"preApprovalExpiry\": 281474976710655,\n    \"authorizationExpiry\": 281474976710655,\n    \"refundExpiry\": 281474976710655,\n    \"minFeeBps\": 0,\n    \"maxFeeBps\": 100,\n    \"feeReceiver\": \"0x...\",\n    \"salt\": \"0x...\"\n  },\n  \"payer\": \"0x...\",\n  \"network\": \"eip155:8453\",\n  \"extra\": {\n    \"escrowAddress\": \"0x...\",\n    \"operatorAddress\": \"0x...\",\n    \"tokenCollector\": \"0x...\"\n  }\n}\n```\n\n**Response:**\n```json\n{\n  \"hasCollectedPayment\": false,\n  \"capturableAmount\": \"1000000\",\n  \"refundableAmount\": \"0\",\n  \"paymentInfoHash\": \"0x...\",\n  \"network\": \"eip155:8453\"\n}\n```\n","operationId":"path_escrow_state","requestBody":{"description":"Escrow state query","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Escrow state","content":{"application/json":{"schema":{"type":"object"},"example":{"hasCollectedPayment":false,"capturableAmount":"1000000","refundableAmount":"0","paymentInfoHash":"0xabcdef...","network":"eip155:8453"}}}},"400":{"description":"Query failed","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/events":{"get":{"tags":["Discovery"],"summary":"Live traffic stream (Server-Sent Events)","description":"Streams one Server-Sent Event per facilitator operation, so observers can render live traffic without scraping logs. The SSE `event:` name is the operation (`verify` or `settle`) and `data:` is the JSON payload below; a `:keepalive` comment every 15s holds the connection open through the load balancer.\n\n`network` is the facilitator's canonical slug, the same one `/supported` uses. A `settle` carries the transaction hash; a `verify` has none, because nothing has settled yet.\n\nThe stream is lossy on purpose: it can never slow down or fail a payment, so a subscriber that falls behind loses events and stays connected. It is also bounded — the endpoint returns **503** with `Retry-After` once the concurrent-subscriber cap is reached, and **404** when the operator has disabled it with `X402_EVENTS_ENABLED=false`.\n\nOperators can narrow what is published without a code change: `X402_EVENTS_DETAIL=minimal` drops `payer`/`tx`/`amount`/`asset`, and `X402_EVENTS_SCOPE=allowlist` restricts the stream to payers in `X402_EVENTS_ALLOWLIST`.\n\nBy default operations that ERROR are not published, so `ok:false` only ever means \"resolved and came back negative\". Set `X402_EVENTS_PUBLISH_FAILURES=true` to emit them; they carry an `error` field holding a bounded CATEGORY (`contract_revert`, `invalid_signature`, `insufficient_funds`, …) and never the error text.","operationId":"path_events","responses":{"200":{"description":"SSE stream of traffic events (`text/event-stream`)","content":{"application/json":{"schema":{"type":"object"},"example":{"ts":1769000000000,"kind":"settle","network":"base","ok":true,"payer":"0x...","tx":"0x...","amount":"20000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","resource":"https://api.example.com/premium-data","payTo":"0x...","description":"Premium data feed","scheme":"exact"}}}},"404":{"description":"Stream disabled by the operator (X402_EVENTS_ENABLED=false)"},"503":{"description":"Concurrent-subscriber cap reached; retry after the `Retry-After` delay"}}}},"/feedback":{"get":{"tags":["ERC-8004"],"summary":"Get feedback submission schema","description":"Returns the JSON schema for ERC-8004 feedback submission requests, including all supported networks and related endpoints.","operationId":"path_feedback_get","responses":{"200":{"description":"Feedback schema with supported networks","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["ERC-8004"],"summary":"Submit reputation feedback","description":"\nSubmits on-chain reputation feedback for an AI agent via the ERC-8004 Reputation Registry (EVM) or Agent Registry with ATOM Engine CPI (Solana).\n\n**Supported networks:** 21 networks (EVM + Solana).\n\n**agentId format:** Numeric (42) for EVM, base58 Pubkey string for Solana. Both JSON numbers and strings are accepted.\n\n**EVM request:**\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"base\",\n  \"feedback\": {\n    \"agentId\": 42,\n    \"value\": 87,\n    \"valueDecimals\": 0,\n    \"tag1\": \"starred\",\n    \"tag2\": \"quality\",\n    \"endpoint\": \"https://agent.example/api\",\n    \"feedbackUri\": \"ipfs://Qm...\"\n  }\n}\n```\n\n**Solana request:**\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"feedback\": {\n    \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n    \"value\": 87,\n    \"valueDecimals\": 0,\n    \"tag1\": \"quality\",\n    \"tag2\": \"api\",\n    \"endpoint\": \"https://agent.example/api\",\n    \"feedbackUri\": \"ipfs://Qm...\",\n    \"score\": 95\n  }\n}\n```\n\n**Proof of payment (anti-sybil gate).** The Reputation Registry lets any address rate any agent, so\na `proof` (the `ProofOfPayment` the settle response already returns) plus a `rater` address turn a\nrating into something backed by a real payment. When both are present the facilitator checks, on-chain\nand server-side: the transaction exists on that network and succeeded, sits in the block the proof\nclaims, contains an ERC-20 `Transfer` of exactly `amount` in `token` from `payer` to `payee`, the payer\nis the `rater`, the payee is an address the Identity Registry ties to the agent (`getAgentWallet` when\nset, otherwise `ownerOf`), the block timestamp is within `ERC8004_PROOF_MAX_AGE_SECS`, `paymentHash`\nrecomputes, and this (payment, agent) pair has not already been spent on a rating.\n\nThe verdict comes back in the response `proof` field with a bounded reason. While\n`ERC8004_REQUIRE_PROOF` is off, a failing proof is reported but the feedback is still written; with it\non, the submission is rejected with 400. Two verdicts never block a write: `proof_rpc_unavailable`\n(no verdict reached - our outage must not erase reputation) and `proof_unverifiable_chain` (a Solana\nfeedback, where the payment half of the gate has no EVM receipt to read).\n\nIf a `feedbackUri` + `feedbackHash` pair is supplied, the facilitator also fetches the document and\nverifies `keccak256(document) == feedbackHash`, reporting whether the anchor is `auditable`,\n`hash_only`, `unreachable` or a `mismatch`. A mismatch is refused: the hash would commit to something\nother than what was shown.\n\nSolana feedback triggers ATOM Engine CPI for trust scoring (trust tiers, HyperLogLog diversity, EMA quality).\n\n**Send `score` (0-100) on Solana.** Without it the engine records the feedback on the\nagent but scores nothing - the program reports `had_impact=false` and reputation stays\nat zero no matter how much feedback accumulates.\n","operationId":"path_feedback_post","requestBody":{"description":"ERC-8004 feedback request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Feedback submission result","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Feedback submission failed","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/evm/prepare":{"post":{"tags":["ERC-8004"],"summary":"Prepare a rater-authored feedback (EVM, EIP-7702)","description":"\nReturns everything the **rater** must sign so that the chain records THEM as the author of the\nrating while the facilitator pays the gas.\n\n**Why this exists.** The Reputation Registry records `msg.sender` as the author and the deployed\nimplementation has no delegation path at all - no `giveFeedbackWithSignature`, no ERC-2771\nforwarder. A rating the facilitator relays normally is a rating attributed to the *facilitator*:\n87,2% of the feedback on Base, and the same address can revoke any of it.\n\nEIP-7702 fixes it without touching the registry. The rater delegates their own EOA to Execution\nMarket's `FeedbackDelegate`, and the facilitator sends the transaction **to the rater's own\naddress**, so the registry observes the rater as `msg.sender`.\n\n`rater` is REQUIRED (EVM address). The response carries:\n\n- `delegate` - the FeedbackDelegate the account must be pointed at\n- `data` - the registry calldata being authorised\n- `digest` - the value the signature must recover against. **The EIP-191 envelope is already\n  applied.** Sign it RAW, as a prehash (`unsafe_sign_hash`, `signHash`, `sign_hash_sync`)\n- `typedData` - **present only on a v4 delegate**, and when it is there, sign IT. The full\n  `eth_signTypedData_v4` payload: the wallet renders the agent, the score, the tags and the\n  deadline as named fields, so the rater sees what they authorise instead of a hex blob. v4\n  carries no `signingPayload` and needs none — `signTypedData` has no envelope to apply twice\n- `signingPayload` - **v3 delegates only.** The same hash with the envelope still OFF, and what a\n  wallet signs there.\n  `personal_sign` / `eth_sign` / `signMessage` apply the envelope themselves, so handing them\n  `digest` wraps it twice and recovers an address that is not the rater -- a well-formed signature\n  that fails with `relay_bad_signature` and no other hint. `keccak256(\"\\\\x19Ethereum Signed\n  Message:\\\\n32\" || signingPayload) == digest`, so a client can check the two against each other\n  instead of rebuilding the preimage from `data`\n- `deadline`, `nonce` - the authorisation window and its single-use value\n- `delegated` - whether the account already carries the delegation; when `false` the submission\n  must include an EIP-7702 `authorization` signed by the rater, and `accountNonce` is the nonce\n  to put in it\n\n**Availability:** only where a `FeedbackDelegate` has actually been deployed and verified on-chain.\nToday that is `base`, `ethereum`, `polygon`, `arbitrum`, `optimism`, `celo`, `bsc`, `monad` and\n`base-sepolia`; other networks answer 400. The delegate takes its registry address through an\nimmutable constructor argument, so its address differs per chain and each one is verified\n(`eth_getCode`, a `REPUTATION_REGISTRY()` read back, and an ERC-165 probe) before it is served.\n\n**Two protocol versions are served in parallel, chosen per chain per request.** The ERC-165 probe\nreports v4 (`0x378a0c90` → EIP-712 `typedData`) or v3 (`0x150b7a02` → the EIP-191 `digest`), and a\ndelegate that answers neither is a superseded v1 and is refused. Nothing about this is pinned to a\nrelease: a chain starts serving `typedData` the moment a v4 delegate is deployed there, with no\ndeploy of ours in between.\n\n`POST /feedback/response/evm/prepare` + `/feedback/response/evm/submit` are the same rail for\n`appendResponse`, and they are **v4 only**: the v3 delegate accepts exactly two selectors and this\nis not one of them, so a v3 network answers 400 `relay_response_needs_v4` instead of falling back\nto the route where the FACILITATOR is the author on record. `clientAddress` and `feedbackIndex`\nare inside the signed struct — without them one signature would answer any client's rating, or any\nrating at that index.\n\nA rater still pointed at a SUPERSEDED version of the delegate is reported as `delegated: false`,\nnot as an error — they sign a fresh authorisation and move to the current version. An account\ndelegated to somebody ELSE's implementation stays a 400: re-pointing it would break whatever\nwallet provider put it there.\n\nThe ERC-165 probe is a **version** check, not a feature check. The delegates are deployed with\nCREATE rather than CREATE2, so an address is a function of (deployer, nonce) and the same address\ncan hold a different version on a different chain. A superseded delegate still has code and is\nstill pinned to the right registry, so without this probe a stale entry would relay silently\nagainst a version that breaks the rater's wallet. Such a delegate is refused with\n`relay_delegate_superseded_version`.\n\n`avalanche` is not on that list and is not waiting to join it: the C-Chain rejects the transaction\ntype itself (`-32000 transaction type not supported`), so relayed feedback is unavailable there by\ndesign. Anchor the rating on a chain that supports EIP-7702 instead; the payment stays where it\nwas made.\n\nDeadlines are deliberately short (default 15 minutes, `ERC8004_RELAY_DEADLINE_SECS`): relaying is\npermissionless by design, so a signed authorisation is live in the wild until it expires.\n","operationId":"path_feedback_evm_prepare","requestBody":{"description":"ERC-8004 feedback request with a `rater`","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Digest and parameters for the rater to sign","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Missing rater, no delegate on this network, or the account is delegated elsewhere","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Could not reach the chain, or the delegate is not usable","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/evm/submit":{"post":{"tags":["ERC-8004"],"summary":"Relay a rater-authored feedback (EVM, EIP-7702)","description":"\nRelays a rating the rater authorised, as an EIP-7702 type-4 transaction sent to the rater's own\naddress. The facilitator pays the gas and never becomes the author.\n\nSend back the same feedback parameters used for `/feedback/evm/prepare`, plus `deadline`, `nonce`,\nthe rater's `signature` over the prepared digest, and - when the account is not delegated yet - the\nEIP-7702 `authorization`:\n\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"base-sepolia\",\n  \"feedback\": { \"...\": \"exactly what you sent to /prepare\" },\n  \"deadline\": 1786400000,\n  \"nonce\": \"0x2222...\",\n  \"signature\": \"0x...\",\n  \"authorization\": {\n    \"chainId\": 84532,\n    \"address\": \"0x3A68085499B62286468A35b7D9Dfc237ef2d3768\",\n    \"nonce\": 7,\n    \"yParity\": 0,\n    \"r\": \"0x...\",\n    \"s\": \"0x...\"\n  }\n}\n```\n\n**The parameters are not redundant.** The facilitator rebuilds the registry calldata from them and\nrecomputes the digest; a signature that does not cover exactly that calldata is refused\n(`relay_bad_signature`). It also refuses an authorization pointing at any delegate other than the\none it offered (`relay_authorization_wrong_delegate`) or signed by anyone other than the rater\n(`relay_authorization_not_by_rater`) - otherwise a caller could have the facilitator pay to delegate\nan account to a contract of their choosing.\n\nThe proof-of-payment gate applies here exactly as it does to `POST /feedback`: who authored a rating\nand whether a payment backs it are separate questions.\n","operationId":"path_feedback_evm_submit","requestBody":{"description":"Feedback parameters, the rater's signature, and the EIP-7702 authorization","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Feedback relayed, authored by the rater","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Signature, authorization, deadline or replay check failed","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"The relayed transaction failed","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/response":{"post":{"tags":["ERC-8004"],"summary":"Append response to feedback","description":"\nAppends a response to existing feedback.\n\n**This is NOT restricted to the agent, and the responder recorded on-chain is the facilitator.**\nVerified against Base mainnet on 2026-08-18 by simulating `appendResponse` on a real feedback\nentry: the call succeeds from an unrelated address, from the agent owner and from the facilitator\nalike (with a negative control — the same call on a non-existent index reverts `index out of\nbounds` — so the probe does distinguish success from failure).\n\nTwo consequences worth stating plainly rather than discovering later:\n\n- the **registry** applies no access control, so anyone can attach a response to anyone's feedback;\n- this **endpoint** is unauthenticated and the facilitator signs the transaction, so the\n  `ResponseAppended` event records the FACILITATOR as `responder`. Real authorship here would need\n  `appendResponse` added to the delegate's selector allowlist, which is a contract change.\n\nDo not read a response as coming from the agent.\n\n**EVM request:**\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"base\",\n  \"agentId\": 42,\n  \"clientAddress\": \"0x...\",\n  \"feedbackIndex\": 1,\n  \"responseUri\": \"ipfs://Qm...\",\n  \"responseHash\": \"0x...\"\n}\n```\n\n**Solana request** (requires `sealHash` for SEAL v1 integrity, `clientAddress` as Solana Pubkey):\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n  \"clientAddress\": \"Bz5K7...\",\n  \"feedbackIndex\": 1,\n  \"responseUri\": \"ipfs://Qm...\",\n  \"responseHash\": \"0x...\",\n  \"sealHash\": \"0xabc123...\"\n}\n```\n","operationId":"path_feedback_response","requestBody":{"description":"Append response request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Response appended","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Failed to append response","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/revoke":{"post":{"tags":["ERC-8004"],"summary":"Revoke feedback (admin only)","description":"\nRevokes previously submitted reputation feedback.\n\n**Requires an `Authorization: Bearer <ERC8004_ADMIN_TOKEN>` header. When the server has no\nERC-8004 admin token configured this route answers 404, so it is indistinguishable from a\nroute that does not exist.**\n\nThe credential is deliberately NOT `BAZAAR_ADMIN_TOKEN`: the registry authorises\n`revokeFeedback` by `msg.sender`, which is the facilitator, so this endpoint can erase any\nfeedback the registry attributes to the facilitator wallet - permanently, and for third\nparties. It is gated separately from the catalog admin surface for that reason.\n\n**EVM request:**\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"base\",\n  \"agentId\": 42,\n  \"feedbackIndex\": 1\n}\n```\n\n**Solana request** needs the SEAL v1 hash of the feedback being revoked. Send the\ncontent under `originalFeedback` and the facilitator derives it; the values must match\nthe original submission exactly.\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n  \"feedbackIndex\": 1,\n  \"originalFeedback\": {\n    \"value\": 95, \"valueDecimals\": 0,\n    \"tag1\": \"uptime\", \"tag2\": \"verify\",\n    \"endpoint\": \"https://api.example.com\",\n    \"feedbackUri\": \"https://example.com/feedback.json\"\n  }\n}\n```\n`sealHash: \"0x...\"` is still accepted if you computed it yourself (keccak256 over the\nprogram's SEAL v1 layout) and takes precedence over `originalFeedback`.\n","operationId":"path_feedback_revoke","parameters":[{"name":"Authorization","in":"header","description":"Bearer <ERC8004_ADMIN_TOKEN>","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Revoke feedback request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Revocation result","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Revocation failed","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Revoke surface disabled (no ERC8004_ADMIN_TOKEN configured)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/solana/prepare":{"post":{"tags":["ERC-8004"],"summary":"Prepare a rater-signed feedback transaction (Solana)","description":"\nBuilds an UNSIGNED Solana transaction whose `client` account is the **rater**, for the rater to sign\nin their own wallet. The facilitator remains the fee payer.\n\n**Why this exists.** Account 0 of the program's `give_feedback` instruction is\n`[signer, writable] client (feedback author / fee payer)`, and `POST /feedback` puts the\n*facilitator's* keypair there - so the chain records the facilitator as the author of the rating,\nnot the person who made it. Solana supports several signers per transaction natively, so the rater\nsigns as `client` while the facilitator still pays. No delegation, no program change.\n\n`rater` is REQUIRED here (base58 pubkey), and the returned transaction expects two signatures: the\nfee payer's (added by `/feedback/solana/submit`) and the rater's.\n\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"feedback\": {\n    \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n    \"rater\": \"9oSLm8Rk1kQ9y8dFcqbAcTNqYqcrTUR6cQ4mL8mYNXpB\",\n    \"value\": 87, \"valueDecimals\": 0, \"score\": 95,\n    \"tag1\": \"quality\", \"tag2\": \"api\"\n  }\n}\n```\n\nThe response carries `transaction` (base64 of the bincode-serialised transaction), `blockhash` and\n`lastValidBlockHeight`. Sign it and send it to `/feedback/solana/submit` before the blockhash expires.\n","operationId":"path_feedback_solana_prepare","requestBody":{"description":"ERC-8004 feedback request with a `rater`","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Unsigned transaction for the rater to sign","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Missing rater, unsupported network, or invalid parameters","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Could not reach the network","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/feedback/solana/submit":{"post":{"tags":["ERC-8004"],"summary":"Submit a rater-signed feedback transaction (Solana)","description":"\nCo-signs a rater-signed feedback transaction as fee payer and sends it.\n\nSend back the SAME feedback parameters used for `/feedback/solana/prepare`, plus the transaction\nwith the rater's signature on it:\n\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"feedback\": { \"...\": \"exactly what you sent to /prepare\" },\n  \"transaction\": \"<base64 of the rater-signed transaction>\"\n}\n```\n\n**The parameters are not redundant.** The facilitator does not sign what it is given: it re-derives\nthe message from those parameters plus the blockhash carried by your submission, and refuses to\nco-sign anything that is not byte-for-byte what it would have offered (`400`, error\n`submitted transaction does not match the one this facilitator built`). Signing arbitrary blobs\nwould turn the fee-payer keypair into a public signing oracle - a single `system_program::transfer`\nwould empty the wallet with the facilitator's signature on it.\n\nThe rater's signature is verified *before* the facilitator adds its own, so a transaction the\nnetwork would reject never costs a fee.\n","operationId":"path_feedback_solana_submit","requestBody":{"description":"Feedback parameters plus the rater-signed transaction","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Feedback submitted, authored by the rater","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Transaction does not match, or the rater's signature is missing or invalid","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"Submission failed","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Returns the health status of the facilitator service.","operationId":"path_health","responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object"},"example":{"status":"healthy"}}}}}}},"/identity/{network}/owner/{address}":{"get":{"tags":["ERC-8004"],"summary":"Resolve an agent by its owner","description":"\nReturns the first agent held by an address, for callers that need to know whether\nan owner already has one before minting another.\n\n**EVM networks:** `balanceOf` then batched `ownerOf` via Multicall3.\n\n**Solana networks:** a `getProgramAccounts` scan filtered by the AgentAccount\ndiscriminator and the `owner` field. `balance` is the number of agents matched.\nThe value read is `AgentAccount.owner`, which the registry caches from the Core\nasset: an asset moved outside the registry's own transfer leaves it stale until\nsomeone calls `sync_owner`.\n\n```json\n{\n  \"agentId\": \"247Y4QLwz9ZbcuHR2nX2EQLZHCsMs1GTqvgd6fpdn85Q\",\n  \"owner\": \"6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv\",\n  \"agentUri\": \"https://example.com/agent.json\",\n  \"network\": \"solana\",\n  \"balance\": \"1\"\n}\n```\n\n**404 means the owner holds nothing; 503 means the lookup could not reach a\nverdict.** Treat them differently: persisting \"not registered\" from a 503 is how\na transient RPC failure becomes a permanent wrong answer, and on a mint path it\nleads to minting a duplicate agent. A 503 carries `\"retryable\": true`.\n","operationId":"path_identity_by_owner","parameters":[{"name":"network","in":"path","description":"Network name (e.g., base, solana, solana-devnet)","required":true,"schema":{"type":"string"}},{"name":"address","in":"path","description":"Owner address: 0x-hex for EVM, base58 for Solana","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Agent found","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid network or address","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Address owns no agent on this network","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Lookup inconclusive, retry","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/identity/{network}/total-supply":{"get":{"tags":["ERC-8004"],"summary":"Get total registered agents","description":"\nReturns the total number of registered agents on a specific network.\n\n**EVM networks** return the ERC-721 totalSupply from the AgentRegistry contract.\nThe ATOM Engine tracks quality through EMA scores, not positive/negative tallies, so\nthere are no such counters.\n\n**Solana networks** read the Metaplex Core collection referenced by the RootConfig PDA:\n`totalSupply` is its `current_size` (net of burns) and `numMinted` its all-time mint count.\nThe registry itself keeps no agent counter on-chain.\n\n**EVM Response:**\n```json\n{\n  \"network\": \"base\",\n  \"totalSupply\": 156\n}\n```\n\n**Solana Response:**\n```json\n{\n  \"network\": \"solana\",\n  \"totalSupply\": 42\n}\n```\n","operationId":"path_identity_total_supply","parameters":[{"name":"network","in":"path","description":"Network name (e.g., ethereum, base, solana)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Total supply","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid or unsupported network","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/identity/{network}/{agent_id}":{"get":{"tags":["ERC-8004"],"summary":"Get agent identity","description":"\nRetrieves agent identity information from the ERC-8004 Identity Registry.\n\n**EVM networks:** ethereum, base, polygon, arbitrum, optimism, celo, bsc, monad, avalanche, scroll + testnets\n\n**Solana networks:** solana, solana-devnet (reads AgentAccount PDA from 8004-solana program)\n\n**EVM Response:**\n```json\n{\n  \"agentId\": 42,\n  \"owner\": \"0x...\",\n  \"agentUri\": \"ipfs://Qm...\",\n  \"agentWallet\": \"0x...\",\n  \"network\": \"base\"\n}\n```\n\n**Solana Response:**\n```json\n{\n  \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n  \"owner\": \"5FHwkrdxPMsgAJBDkWmcoLiN9m1K95VCGw7qr4eXfjsP\",\n  \"agentUri\": \"https://example.com/agent.json\",\n  \"nftName\": \"My AI Agent\",\n  \"feedbackCount\": 47,\n  \"network\": \"solana\"\n}\n```\n","operationId":"path_identity","parameters":[{"name":"network","in":"path","description":"Network name (e.g., ethereum, base, solana, solana-devnet)","required":true,"schema":{"type":"string"}},{"name":"agent_id","in":"path","description":"Agent ID: numeric for EVM (e.g., 42), base58 Pubkey for Solana","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Agent identity","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid network or agent","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Agent not found","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/identity/{network}/{agent_id}/metadata/{key}":{"get":{"tags":["ERC-8004"],"summary":"Read agent metadata","description":"\nReads a specific metadata key from an agent's Identity Registry entry.\n\nSupports both EVM and Solana networks. On Solana, metadata is stored in MetadataEntryPda accounts derived from the agent's NFT address and metadata key hash.\n\n**Response:**\n```json\n{\n  \"agentId\": 42,\n  \"key\": \"description\",\n  \"value\": \"0x48656c6c6f\",\n  \"valueUtf8\": \"Hello\",\n  \"network\": \"base\"\n}\n```\n","operationId":"path_identity_metadata","parameters":[{"name":"network","in":"path","description":"Network name (e.g., ethereum, base, solana)","required":true,"schema":{"type":"string"}},{"name":"agent_id","in":"path","description":"Agent ID: numeric for EVM, base58 Pubkey for Solana","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","description":"Metadata key (e.g., description, website, version)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Metadata value","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid network or agent","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Agent or metadata key not found","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/index.md":{"get":{"tags":["Agentic"],"summary":"Landing page in Markdown","description":"The landing page as text, for an agent that would rather not render a 240 KB HTML monolith to learn what this service is.","operationId":"path_index_md","responses":{"200":{"description":"Markdown overview","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/llms-full.txt":{"get":{"tags":["Agentic"],"summary":"llms.txt, index.md, skill.md and auth.md in one file","description":"The whole agent-facing documentation set concatenated, for pasting into one context window. Generated by `scripts/build_llms_full.sh`; a test fails the build when it drifts from its sources.","operationId":"path_llms_full_txt","responses":{"200":{"description":"Plain-text bundle","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms.txt":{"get":{"tags":["Agentic"],"summary":"llms.txt site map for LLMs","description":"The llmstxt.org map of this service: what it is, what it costs (nothing), which networks and schemes it settles, and where every other machine-readable document lives.","operationId":"path_llms_txt","responses":{"200":{"description":"Plain-text map","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/mcp":{"get":{"tags":["MCP"],"summary":"No SSE stream here","description":"Always 405 with `Allow: POST`. This MCP server is stateless, so there is no server-initiated event stream to subscribe to. The body is JSON, not text, so a scanner grading content types does not read it as a broken surface.","operationId":"path_mcp_get","responses":{"405":{"description":"Use POST","content":{"application/json":{"schema":{"type":"object"},"example":{"error":"GET is not supported on /mcp","transport":"streamable-http","method":"POST"}}}}}},"post":{"tags":["MCP"],"summary":"MCP endpoint (JSON-RPC 2.0 over Streamable HTTP)","description":"\nThe facilitator as an MCP server. Stateless Streamable HTTP: every request is a\nJSON-RPC 2.0 document, there is no session id, and `GET /mcp` answers 405 because\nthere is no server-initiated stream to open.\n\n**Tools** (each one is dispatched through the REST handler it names, so an MCP call\nand the HTTP call it stands for cannot answer differently):\n\n| Tool | Is | Moves money |\n|---|---|---|\n| `x402_supported` | `GET /supported` | no |\n| `x402_accepts` | `POST /accepts` | no |\n| `x402_verify` | `POST /verify` | no |\n| `x402_settle` | `POST /settle` | yes, irreversibly |\n\nA tool's `arguments` are the JSON body of the request it stands for; its result is\nthat request's response body verbatim in one text content block. A non-2xx answer\ncomes back as `isError: true` carrying the facilitator's own message, not as a\nJSON-RPC error.\n\nThe body is the only channel: a tool call cannot set headers. The one exception is\n`x402_settle`, which takes an optional `idempotencyKey` argument that is lifted out\nof the body and sent as the `Idempotency-Key` header, so an MCP client can ask for\nexactly-once the same way an HTTP client does. The v2 `PAYMENT-SIGNATURE` header\ntransport has no equivalent here; send the payload in the body.\n\n**`Accept` must name BOTH `application/json` and `text/event-stream`.** The MCP\nStreamable HTTP transport requires it and answers `406` otherwise, even though this\nserver is stateless and always replies with JSON. Use\n`accept: application/json, text/event-stream`.\n\n**Authentication:** none, same as every other route (`/auth.md`).\n**Rate limit:** shared with `POST /verify` and `POST /settle` -- one per-IP bucket,\nnot two.\n\nHandshake (note the two Accept types):\n\n```json\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\n  \"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\n  \"clientInfo\":{\"name\":\"my-agent\",\"version\":\"1.0\"}}}\n```\n\nTool call:\n\n```json\n{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\n  \"name\":\"x402_supported\",\"arguments\":{}}}\n```\n","operationId":"path_mcp_post","requestBody":{"description":"A JSON-RPC 2.0 request: initialize, tools/list, tools/call, ping","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"JSON-RPC 2.0 response","content":{"application/json":{"schema":{"type":"object"},"example":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"x402-facilitator","version":"0.0.0"}}}}}},"400":{"description":"Not a JSON-RPC document","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"description":"Host header not on the MCP allowlist (MCP_ALLOWED_HOSTS)","content":{"application/json":{"schema":{"type":"object"}}}},"406":{"description":"Accept did not name both application/json and text/event-stream","content":{"application/json":{"schema":{"type":"object"},"example":{"error":"Not Acceptable","status":406,"hint":"Accept must name BOTH application/json and text/event-stream, e.g. `accept: application/json, text/event-stream`."}}}},"415":{"description":"Content-Type is not application/json","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Per-IP rate limit, shared with /verify and /settle","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/openapi.json":{"get":{"tags":["Agentic"],"summary":"This document","description":"The OpenAPI specification, at the root path scanners and RFC 9727 catalogs look for. Identical to `/api-docs/openapi.json`, which is where Swagger UI reads it.","operationId":"path_openapi_json","responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"The document could not be serialised","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/register":{"get":{"tags":["ERC-8004"],"summary":"Get agent registration schema","description":"Returns the JSON schema for ERC-8004 agent registration requests, including supported networks and body format.","operationId":"path_register_get","responses":{"200":{"description":"Registration schema","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["ERC-8004"],"summary":"Register a new agent","description":"\nRegisters a new ERC-8004 agent on-chain. The facilitator pays all gas fees.\n\n**Supported networks:** 21 networks (EVM + Solana). EVM chains use ERC-721 NFTs, Solana uses Metaplex Core NFTs.\n\n**EVM request:**\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"base\",\n  \"agentUri\": \"ipfs://Qm.../agent.json\",\n  \"metadata\": [{\"key\": \"description\", \"value\": \"0x...\"}],\n  \"recipient\": \"0x...\"\n}\n```\n\n**Solana request:** `recipient` is a base58 Solana address.\n```json\n{\n  \"x402Version\": 1,\n  \"network\": \"solana\",\n  \"agentUri\": \"ipfs://Qm.../agent.json\",\n  \"metadata\": [{\"key\": \"x402Support\", \"value\": \"true\"}],\n  \"recipient\": \"6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv\"\n}\n```\n\nOn Solana the facilitator mints, initializes the agent's ATOM stats account, then\ntransfers the Metaplex Core asset to `recipient`, paying every fee. The ordering is\nrequired: only the owner can initialize the stats, so it happens before the transfer.\nWithout that account the ATOM Engine records feedback but scores none of it.\n\n`agentWallet` does not survive the transfer and must be re-set by the new owner,\nthe same as on EVM.\n\nIf the mint succeeds but the transfer fails, the response is a 500 that still carries\n`agentId` and `transaction`: the agent exists and is held by the facilitator, and is\nnever reported as delivered.\n\n**EVM response:** `agentId` is a numeric string (ERC-721 tokenId).\n**Solana response:** `agentId` is a base58 Pubkey (Metaplex Core NFT mint address).\n\n```json\n{\n  \"success\": true,\n  \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n  \"transaction\": \"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d6...\",\n  \"owner\": \"facilitator-pubkey...\",\n  \"network\": \"solana\"\n}\n```\n\n**Async mode (EVM):** send header `Prefer: respond-async` (or `X-Async: true`) to\nget an immediate `202 Accepted` with a `jobId` instead of blocking on the ~28s\non-chain confirmation. Poll `GET /register/status/{jobId}` until `status` is\n`done` and `agentId` is populated. The `Location` header of the 202 points at the\nstatus URL, keeping the facilitator's on-chain latency out of the caller's\ntimeout budget.\n\n**Idempotency / in-flight lock:** a second registration for the same\n`network|agentUri|recipient` while the first is still confirming is not\nre-minted — the async path returns the existing job, the sync path returns\n`409 Conflict`.\n","operationId":"path_register_post","requestBody":{"description":"Agent registration request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Registration result (sync)","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Async registration accepted; poll /register/status/{jobId}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Registration failed","content":{"application/json":{"schema":{"type":"object"}}}},"409":{"description":"A registration for this agent is already in progress","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/register/status/{job_id}":{"get":{"tags":["ERC-8004"],"summary":"Poll async registration status","description":"\nReturns the status of an asynchronous ERC-8004 registration started with\n`Prefer: respond-async` on `POST /register`.\n\n`status` progresses `pending -> mint_confirmed -> done` (or `failed`). Once\n`mint_confirmed`/`done`, `agentId` is populated. Terminal jobs are retained for\none hour before they age out (then this returns `404`).\n\n```json\n{\n  \"jobId\": \"reg_42\",\n  \"status\": \"done\",\n  \"network\": \"base\",\n  \"agentId\": \"17\",\n  \"transaction\": \"0x...\",\n  \"transferTransaction\": \"0x...\",\n  \"owner\": \"0x...\"\n}\n```\n","operationId":"path_register_status","parameters":[{"name":"job_id","in":"path","description":"Job id from the async POST /register","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current job status","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Job not found or expired","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/reputation/{network}/{agent_id}":{"get":{"tags":["ERC-8004"],"summary":"Get agent reputation","description":"\nQueries the reputation summary for an AI agent from the ERC-8004 Reputation Registry.\n\n**EVM networks:** ethereum, base, polygon, arbitrum, optimism, celo, bsc, monad, avalanche, scroll + testnets\n\n**Solana networks:** solana, solana-devnet (reads from ATOM Engine for enriched reputation data)\n\n**Client address filtering (EVM only):** The `clientAddresses` query parameter accepts comma-separated Ethereum addresses to filter reputation data by specific clients. If omitted, the endpoint auto-discovers all clients who have given feedback via the on-chain `getClients()` function.\n\n**Examples:**\n- `/reputation/base/42` - EVM agent (all clients, auto-discovered)\n- `/reputation/solana/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv` - Solana agent (with ATOM stats)\n- `/reputation/base/42?includeFeedback=true&tag1=quality` - with feedback entries filtered by tag\n\n**EVM Response:**\n```json\n{\n  \"agentId\": 42,\n  \"summary\": { \"count\": 15, \"summaryValue\": 87, \"summaryValueDecimals\": 0 },\n  \"feedback\": [...],\n  \"atomStats\": null,\n  \"network\": \"base\"\n}\n```\n\n**Solana Response (includes ATOM Engine bonus data):**\n```json\n{\n  \"agentId\": \"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkv\",\n  \"summary\": { \"count\": 47, \"summaryValue\": 78, \"summaryValueDecimals\": 0 },\n  \"atomStats\": {\n    \"trustTier\": 3, \"trustTierName\": \"Trusted\",\n    \"qualityScore\": 78, \"loyaltyScore\": 64, \"confidence\": 85, \"riskScore\": 12,\n    \"diversityRatio\": 67, \"minScore\": 40, \"maxScore\": 99, \"lastScore\": 95,\n    \"feedbackCount\": 47, \"lastFeedbackSlot\": 301118422\n  },\n  \"network\": \"solana\"\n}\n```\n","operationId":"path_reputation","parameters":[{"name":"network","in":"path","description":"Network name (e.g., ethereum, base, solana, solana-devnet)","required":true,"schema":{"type":"string"}},{"name":"agent_id","in":"path","description":"Agent ID: numeric for EVM (e.g., 42), base58 Pubkey for Solana","required":true,"schema":{"type":"string"}},{"name":"include_feedback","in":"query","description":"Include individual feedback entries","required":false,"schema":{"type":"boolean"}},{"name":"client_addresses","in":"query","description":"Comma-separated client addresses to filter by. If omitted, auto-discovers all clients via getClients()","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Reputation data","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid network or agent","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Agent not found","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/robots.txt":{"get":{"tags":["Agentic"],"summary":"Crawler policy","description":"RFC 9309 policy with every AI crawler allowed explicitly, Content-Signal set to yes on all three signals, and no Disallow: this service is public payment infrastructure with no paid routes to hide.","operationId":"path_robots_txt","responses":{"200":{"description":"Crawler policy","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/settle":{"get":{"tags":["Core"],"summary":"Get settlement schema","description":"Returns the JSON schema for payment settlement requests.","operationId":"path_settle_get","responses":{"200":{"description":"Settlement schema","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Core"],"summary":"Settle payment on-chain","description":"\nSubmits a verified payment authorization to the blockchain for settlement.\n\n**Process:**\n1. Verifies the payment (same as /verify)\n2. Calls `transferWithAuthorization` on the token contract\n3. Returns transaction hash on success\n\n**Upto Settlement (scheme: \"upto\"):**\n\nWhen `scheme: \"upto\"`, the client provides a Permit2-signed authorization for a maximum amount.\nThe server settles for the actual usage amount (<= authorized max). If actual amount is 0, no\non-chain transaction is submitted.\n\nUses `x402UptoPermit2Proxy.settle(permit, amount, owner, witness, signature)` via Uniswap Permit2.\n\n**Escrow / Commerce Lifecycle (scheme: \"escrow\" or \"commerce\"):**\n\nBoth `\"escrow\"` and `\"commerce\"` schemes are supported and functionally identical.\nThe `\"commerce\"` alias was introduced by x402r for marketplace integrations (e.g., Execution Market).\nThe `action` field controls the operation:\n\n| Action | Description | Signature Required |\n|--------|-------------|-------------------|\n| `authorize` (default) | Lock funds in escrow | Yes (ERC-3009, by the payer) |\n| `release` | Send escrowed funds to receiver | EIP-712 lifecycle order by the payer or the operator owner (`payload.lifecycleAuth`) |\n| `refundInEscrow` | Return escrowed funds to payer | EIP-712 lifecycle order by the receiver, the operator owner, or the payer once `authorizationExpiry` has passed |\n\nEscrow contracts deployed on 11 networks. See `/supported` for networks with active PaymentOperator deployments.\n\n**Lifecycle orders.** `release` and `refundInEscrow` carry no ERC-3009 signature (the funds are\nalready escrowed) but they do move money, so they carry `payload.lifecycleAuth`: an EIP-712\nsignature over `LifecycleOrder(string action, uint256 amount, uint256 deadline, bytes32 nonce, PaymentInfo paymentInfo)`\nwith domain `{ name: \"x402 escrow lifecycle\", version: \"1\", chainId }` and `PaymentInfo` the\nAuthCaptureEscrow type verbatim. The \"operator owner\" is the operator's `FEE_RECIPIENT()`, read\non chain. Whether the order is required is governed by `ESCROW_LIFECYCLE_AUTH` (`off` | `log` |\n`enforce`); `GET /settle` publishes the effective mode. Under `enforce` a missing or invalid order\nis 403 with a bounded `errorReason` (`missing`, `bad_signature`, `expired`, `deadline_too_far`,\n`replayed`, `unauthorized_role`); `owner_unverifiable` is 502 and retryable.\n\n**Escrow release/refund payload**:\n```json\n{\n  \"scheme\": \"escrow\",\n  \"action\": \"release\",\n  \"payload\": {\n    \"paymentInfo\": { \"operator\": \"0x...\", \"receiver\": \"0x...\", ... },\n    \"payer\": \"0x...\",\n    \"amount\": \"1000000\",\n    \"lifecycleAuth\": {\n      \"signer\": \"0x...\",\n      \"deadline\": 1757088000,\n      \"nonce\": \"0x<32 bytes>\",\n      \"signature\": \"0x<65 bytes>\"\n    }\n  },\n  \"paymentRequirements\": {\n    \"network\": \"eip155:8453\",\n    \"extra\": { \"escrowAddress\": \"0x...\", \"operatorAddress\": \"0x...\", \"tokenCollector\": \"0x...\" }\n  }\n}\n```\n\n**Response on success:**\n```json\n{\n  \"success\": true,\n  \"transaction\": \"0x...\",\n  \"transactionHash\": \"0x...\",\n  \"paymentId\": \"0x...\",\n  \"network\": \"base\",\n  \"payer\": \"0x...\"\n}\n```\n\n`transaction`, `transactionHash` and `transaction_hash` are the same value under\nthe three names clients read it by. `paymentId` is `keccak256(caip2 || txHash)`,\nthe key DX402 evidence is stored under, so it is what `/dx402/evidence/{paymentId}`\nand `/dx402/receipt/{paymentId}` take.\n\nSend an `Idempotency-Key` header to make a retry safe: the same key with the same\nbody replays the first response (marked `Idempotent-Replayed: true`), the same\nkey with a different body is refused with `409`, and an unreachable idempotency\nstore fails closed with `503` rather than settling something it could not\ndeduplicate.\n\n**Response on failure:**\n```json\n{\n  \"success\": false,\n  \"errorReason\": \"insufficient_funds\",\n  \"payer\": \"0x...\",\n  \"network\": \"base\"\n}\n```\n\n**Response when the transaction was broadcast and never confirmed** (`502`):\n```json\n{\n  \"error\": \"settlement_unconfirmed\",\n  \"transaction\": \"0x...\",\n  \"paymentId\": \"0x...\",\n  \"retryable\": false\n}\n```\n\nThis is not a verdict. The transaction may be mined; the facilitator waited for a\nreceipt and never got one. `retryable` is `false` and is load-bearing: retrying\nre-signs a **fresh** authorization for the same purchase, which is a new and\nperfectly valid payment that the token's own EIP-3009 nonce check cannot stop, so\na retry here is how a buyer pays twice. Look the `transaction` up on chain\ninstead. `paymentId` is derived exactly as on the success path, so a transaction\nlater found confirmed carries the same identifier.\n\nDo not collapse this with the other `502`, `upstream_rpc_unavailable`, which\ncarries `Retry-After` and is a plain upstream failure. Branch on `error`.\n\n**Envelope shapes.** `/settle` and `/verify` share one parser, so both the x402\nv1 envelope (`paymentPayload` + `paymentRequirements`) and the x402 v2 envelope\n(`paymentPayload` + `resource` + `accepted`, no `paymentRequirements`) are\naccepted here on identical terms. Both are written out under `POST /verify`.\n","operationId":"path_settle_post","requestBody":{"description":"x402 settle request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Settlement result","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Settlement failed","content":{"application/json":{"schema":{"type":"object"}}}},"502":{"description":"`settlement_unconfirmed`: the transaction was broadcast and no receipt                            arrived, so it may be mined -- the body carries `transaction` and                            `paymentId` and `retryable: false`. (Also `upstream_rpc_unavailable`,                            which is retryable and carries `Retry-After`.)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/sitemap.xml":{"get":{"tags":["Agentic"],"summary":"Sitemap","description":"The pages a reader can land on: the HTML pages and the agent-facing Markdown documents.","operationId":"path_sitemap_xml","responses":{"200":{"description":"Sitemap","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/skill.md":{"get":{"tags":["Agentic"],"summary":"Agent operating manual","description":"How to call verify and settle: the request and response shapes, the five schemes, the per-chain EIP-712 domain-name trap, and which failures mean retry rather than stop.","operationId":"path_skill_md","responses":{"200":{"description":"Markdown manual","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/supported":{"get":{"tags":["Discovery"],"summary":"List supported payment kinds","description":"\nReturns all supported payment kinds (network + scheme + version combinations).\n\n**Schemes:** every one of them is advertised under BOTH ways of naming a chain (v1 and v2 formats).\n- `exact` - Direct EIP-3009 payment settlement\n- `upto` - Permit2-based variable amount settlement. Client authorizes a max amount; server settles actual usage (<= max). Ideal for usage-based pricing (LLM tokens, bandwidth, metered APIs).\n- `escrow` / `commerce` - x402r PaymentOperator escrow\n- `fhe_transfer` - FHE encrypted transfer via Zama\n\nUntil 2026-09-03 `escrow`, `commerce` and `upto` appeared **only** under CAIP-2 network ids, so a client that discovered schemes by reading the v1 entries concluded this facilitator had no escrow at all. They now appear under both.\n\n**Upto networks (11):** Base, Optimism, Arbitrum, Polygon, BSC, Ethereum, HyperEVM, Monad, Base Sepolia, Avalanche Fuji, Arbitrum Sepolia — via the x402UptoPermit2Proxy contract (Permit2-based, canonical CREATE2 address `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002`).\n\n`upto` is **not** available on every EVM network that supports `exact`. The proxy address is identical on all chains because it is deployed with CREATE2, but the deployment still has to be replayed per chain, and on Avalanche, Celo, Scroll, Unichain and Optimism Sepolia it never was — the address has no code there. Query `/supported` rather than assuming: it now lists `upto` only where settlement can actually succeed.\n\n**Escrow networks (9 total):** Base, Ethereum, Polygon, Arbitrum, Celo, Monad, Avalanche, Base Sepolia, Ethereum Sepolia.\nOnly networks with a deployed PaymentOperator appear in the response.\n\n**Extensions:** the `extensions` array lists what this deployment actually serves — `bazaar`, and `durable-evidence` **only when DX402 is serviceable** (enabled *and* its store and index are configured), so no client builds against `/dx402/*` routes that would 404.\n\n**Response includes both v1 and v2 formats:**\n- v1: `\"network\": \"base\"` (string enum)\n- v2: `\"network\": \"eip155:8453\"` (CAIP-2 format)\n\nThe two entries for one chain are separate objects. `networkAliases` is what ties them together: it lists every identifier naming that same chain, the entry's own included, so a reader never has to pair them by guesswork. The field is optional and additive - it is absent for a network the facilitator cannot resolve, and no other field changed shape or name.\n","operationId":"path_supported","responses":{"200":{"description":"Supported payment kinds","content":{"application/json":{"schema":{"type":"object"},"example":{"kinds":[{"x402Version":1,"scheme":"exact","network":"base","networkAliases":["base","eip155:8453"]},{"x402Version":2,"scheme":"exact","network":"eip155:8453","networkAliases":["base","eip155:8453"]},{"x402Version":1,"scheme":"upto","network":"base","networkAliases":["base","eip155:8453"]},{"x402Version":2,"scheme":"upto","network":"eip155:8453","networkAliases":["base","eip155:8453"]},{"x402Version":2,"scheme":"escrow","network":"eip155:8453","networkAliases":["base","eip155:8453"],"extra":{"escrowAddress":"0xb9488351E48b23D798f24e8174514F28B741Eb4f","operatorAddress":"0x...","tokenCollector":"0x48ADf6E37F9b31dC2AAD0462C5862B5422C736B8"}}]}}}}}}},"/transactions":{"get":{"tags":["Discovery"],"summary":"Recent operations the facilitator recorded","description":"Recent verify/settle operations, newest first.\n\n**This is an index, not a ledger.** The record is written best-effort AFTER the operation resolved, so an unreachable store loses rows and never blocks a payment. The chain is authoritative; a row missing here does not mean the payment did not happen.\n\nCounting starts when the store was enabled — earlier operations are absent, not zero. `limit` is capped at 200.","operationId":"path_transactions","parameters":[{"name":"limit","in":"query","description":"Rows to return (1-200, default 50)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"network","in":"query","description":"Canonical slug, e.g. `base`","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recent operations","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Transaction store unavailable or not configured"}}}},"/verify":{"get":{"tags":["Core"],"summary":"Get verification schema","description":"Returns the JSON schema for payment verification requests.","operationId":"path_verify_get","responses":{"200":{"description":"Verification schema","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Core"],"summary":"Verify payment authorization","description":"\nVerifies an x402 payment authorization without settling it on-chain.\n\n**Checks performed:**\n- Payload structure validation\n- EIP-712 signature verification\n- Nonce validity\n- Amount matching\n- Timestamp validity (validAfter/validBefore)\n- Token and network support\n\n**Request body:**\n```json\n{\n  \"x402Version\": 1,\n  \"paymentPayload\": {\n    \"x402Version\": 1,\n    \"scheme\": \"exact\",\n    \"network\": \"base\",\n    \"payload\": {\n      \"signature\": \"0x111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221b\",\n      \"authorization\": {\n        \"from\": \"0x0000000000000000000000000000000000000001\",\n        \"to\": \"0x0000000000000000000000000000000000000002\",\n        \"value\": \"1000000\",\n        \"validAfter\": \"1700000000\",\n        \"validBefore\": \"1700100000\",\n        \"nonce\": \"0x0000000000000000000000000000000000000000000000000000000000000001\"\n      }\n    }\n  },\n  \"paymentRequirements\": {\n    \"scheme\": \"exact\",\n    \"network\": \"base\",\n    \"maxAmountRequired\": \"1000000\",\n    \"resource\": \"https://example.com/protected\",\n    \"description\": \"One API call\",\n    \"mimeType\": \"application/json\",\n    \"payTo\": \"0x0000000000000000000000000000000000000002\",\n    \"maxTimeoutSeconds\": 60,\n    \"asset\": \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\"\n  }\n}\n```\n\nThat body is runnable as printed. The signature and nonce are well-formed\nplaceholders, not a real authorization, so copied verbatim it answers `200` with\n`\"isValid\": false` and `\"invalidReason\": \"invalid_signature\"`.\n\n`invalidReason` is a snake_case token, one per cause: `invalid_signature`,\n`invalid_timing`, `insufficient_funds`, `insufficient_value`,\n`receiver_mismatch`, `invalid_network`, `invalid_scheme`,\n`unexpected_settle_error`. Switch on it, but keep a default arm -- the list is\nopen. It was `null` for every cause before 2.13.0.\n\n**Five things in that shape are load-bearing:**\n- `paymentPayload` carries its own `x402Version`, `scheme` and `network` at its root.\n- The signed data sits under `payload.authorization`, not directly under `payload`.\n- The authorization's amount field is `value` (`maxAmountRequired` is the\n  requirements' name for its own limit).\n- `validAfter` and `validBefore` are **strings**: `\"1700000000\"`, not `1700000000`.\n- `resource`, `description`, `mimeType` and `maxTimeoutSeconds` are required in\n  `paymentRequirements` and have no defaults.\n\n`network` is accepted in either spelling, in both objects: the x402 v1 name\n(`\"base\"`) or the CAIP-2 identifier (`\"eip155:8453\"`). This is what lets an offer\ntaken straight from `/discovery/resources`, which is CAIP-2, be paid unmodified.\n\n**The x402 v2 request body:**\n```json\n{\n  \"x402Version\": 2,\n  \"paymentPayload\": {\n    \"x402Version\": 2,\n    \"payload\": {\n      \"signature\": \"0x111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221b\",\n      \"authorization\": {\n        \"from\": \"0x0000000000000000000000000000000000000001\",\n        \"to\": \"0x0000000000000000000000000000000000000002\",\n        \"value\": \"1000000\",\n        \"validAfter\": \"1700000000\",\n        \"validBefore\": \"1700100000\",\n        \"nonce\": \"0x0000000000000000000000000000000000000000000000000000000000000001\"\n      }\n    }\n  },\n  \"resource\": {\n    \"url\": \"https://example.com/protected\",\n    \"description\": \"One API call\",\n    \"mimeType\": \"application/json\"\n  },\n  \"accepted\": {\n    \"scheme\": \"exact\",\n    \"network\": \"eip155:8453\",\n    \"amount\": \"1000000\",\n    \"payTo\": \"0x0000000000000000000000000000000000000002\",\n    \"maxTimeoutSeconds\": 60,\n    \"asset\": \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\"\n  }\n}\n```\n\nv2 is a **different envelope**, not the v1 one with a `2` in it. There is no\n`paymentRequirements`: the requirements split into `accepted` (what is charged:\n`scheme`, `network`, `asset`, `amount`, `payTo`, `maxTimeoutSeconds`, optional\n`extra`) and `resource` (what is sold: `url`, `description`, `mimeType`), both at\nthe top level. `maxAmountRequired` becomes `accepted.amount`, and\n`paymentPayload` no longer carries `scheme`/`network` at its root — they live in\n`accepted`. Unknown keys in `accepted` are ignored, so a `402` offer that also\ncarries `maxAmountRequired` or `resource` can be forwarded unedited.\n\n`accepted.network` is **CAIP-2 only** — the one field where the two spellings are\nnot interchangeable. The v1 `paymentRequirements.network` and\n`paymentPayload.network` take `\"base\"` or `\"eip155:8453\"`; `accepted.network`\ntakes `\"eip155:8453\"` and refuses `\"base\"`.\n\nThe two bodies above are the same payment written two ways, and they reduce to\nthe same internal request. Both are runnable as printed.\n\n**On the inner copy of `resource`/`accepted`.** Earlier builds also required them\nrepeated *inside* `paymentPayload` and answered `400 data did not match any\nvariant of untagged enum VerifyRequestEnvelope` without it. The inner copy is now\noptional and derived from the outer pair when absent; sending it still works\nunchanged.\n\n`POST /settle` takes exactly this body, in either version.\n","operationId":"path_verify_post","requestBody":{"description":"x402 verify request","content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object"},"example":{"isValid":true}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object"},"example":{"error":"Invalid signature"}}}}}}},"/version":{"get":{"tags":["Discovery"],"summary":"Get facilitator version","description":"Returns the current version of the facilitator. The version always matches the Cargo.toml package version.","operationId":"path_version","responses":{"200":{"description":"Version info","content":{"application/json":{"schema":{"type":"object"},"example":{"version":"(current Cargo.toml version)"}}}}}}},"/workflows.json":{"get":{"tags":["Agentic"],"summary":"Workflow manifest","description":"The four state machines this facilitator drives (one-shot payment, two-phase escrow, asynchronous ERC-8004 registration, feedback prepare/submit), with the operation that triggers each transition.","operationId":"path_workflows_json","responses":{"200":{"description":"Workflow manifest","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{},"tags":[{"name":"Core","description":"Core x402 payment verification and settlement (exact, upto, escrow schemes)"},{"name":"Escrow","description":"Gasless escrow lifecycle (authorize, release, refund, state query)"},{"name":"Discovery","description":"Network and scheme discovery"},{"name":"ERC-8004","description":"AI Agent reputation and identity (ERC-8004 Trustless Agents) - 21 networks (EVM + Solana)"},{"name":"Bazaar","description":"Decentralized resource discovery registry"},{"name":"Compliance","description":"OFAC compliance and sanctions screening"},{"name":"Health","description":"Service health and status"},{"name":"Agentic","description":"Machine-readable discovery surfaces (llms.txt, A2A card, x402 discovery, RFC 9727 catalog, skills index, MCP server card)"},{"name":"MCP","description":"Model Context Protocol server (Streamable HTTP, stateless) exposing verify/settle/supported/accepts as tools"}]}