{
  "name": "x402 Marketplace",
  "description": "A catalog of HTTP 402 payment endpoints on the Radius testnet for testing x402 payment clients. Each skill is an endpoint an agent can pay for and call.",
  "protocol": "x402",
  "builtBy": {
    "name": "Radius",
    "url": "https://radiustech.xyz",
    "context": "Built for Radius workshop",
    "tagline": "Stablecoin-native settlement infrastructure for the agentic internet",
    "description": "Radius is stablecoin-native settlement infrastructure for the agentic internet, settling machine-to-machine micropayments at the scale of API calls, paid content, and streaming compute — with sub-second finality, near-zero transaction costs, EVM-compatible execution, stablecoin fees, and no native token. Radius provides first-party x402 facilitator infrastructure (Permit2 settlement, EIP-2612 gas sponsoring) for HTTP 402 payments on Radius mainnet and testnet."
  },
  "client": "radius-cli",
  "clients": [
    "radius-cli",
    "scripts/pay.ts (chain-agnostic viem script)"
  ],
  "network": {
    "caip2": "eip155:72344",
    "chainId": 72344
  },
  "asset": {
    "symbol": "SBC",
    "address": "0x33ad9e4BD16B69B5BFdED37D8B5D9fF9aba014Fb",
    "decimals": 6
  },
  "facilitator": "https://facilitator.testnet.radiustech.xyz",
  "payTo": "0x05354779F8E5397624928a461b97b190CAFcc23B",
  "discovery": {
    "catalog": "https://grug402.dev/api/x402/catalog",
    "resources": "https://grug402.dev/api/x402/discovery/resources",
    "openapi": "https://grug402.dev/openapi.yaml",
    "llms": "https://grug402.dev/llms.txt"
  },
  "skills": [
    {
      "id": "compliant-get",
      "name": "Canonical GET (real verify and settle)",
      "description": "The textbook x402 flow: an unpaid GET gets a 402 with a PAYMENT-REQUIRED challenge, then the server runs a real /verify and /settle against the Radius testnet facilitator.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/compliant-get",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/compliant-get",
      "documentation": "https://grug402.dev/scenario/compliant-get.md"
    },
    {
      "id": "compliant-post",
      "name": "Canonical POST with body",
      "description": "The same compliant flow as the canonical GET, for a POST that carries a JSON body.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/compliant-post",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": {
        "query": "radius"
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/compliant-post -H 'Content-Type: application/json' -d '{\"query\":\"radius\"}'",
      "documentation": "https://grug402.dev/scenario/compliant-post.md"
    },
    {
      "id": "compliant-body-get",
      "name": "Body-primary GET challenge",
      "description": "Returns a valid x402 challenge in the response body only (no PAYMENT-REQUIRED header). Clients that read the challenge from the JSON body rather than decoding the base64 header must handle this path.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/compliant-body-get",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/compliant-body-get",
      "documentation": "https://grug402.dev/scenario/compliant-body-get.md"
    },
    {
      "id": "compliant-body-post",
      "name": "Body-primary POST challenge",
      "description": "The body-primary happy path for a POST endpoint. Challenge is delivered in the JSON response body only, no PAYMENT-REQUIRED header.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/compliant-body-post",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": {
        "query": "radius"
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/compliant-body-post -H 'Content-Type: application/json' -d '{\"query\":\"radius\"}'",
      "documentation": "https://grug402.dev/scenario/compliant-body-post.md"
    },
    {
      "id": "cheapest",
      "name": "Minimal price (1 atomic unit)",
      "description": "A compliant endpoint priced at the smallest atomic unit, 0.000001 SBC.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/cheapest",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "1",
        "sbc": "0.000001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/cheapest",
      "documentation": "https://grug402.dev/scenario/cheapest.md"
    },
    {
      "id": "multi-accepts",
      "name": "Single testnet accepts entry",
      "description": "Advertises one testnet requirement that a correct client selects by matching its network.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/multi-accepts",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/multi-accepts",
      "documentation": "https://grug402.dev/scenario/multi-accepts.md"
    },
    {
      "id": "idempotent-retry",
      "name": "Correct idempotency (cached retry)",
      "description": "Caches the result by payment id, so retrying the same payment returns the cached 200 without charging twice.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/idempotent-retry",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/idempotent-retry",
      "documentation": "https://grug402.dev/scenario/idempotent-retry.md"
    },
    {
      "id": "weather-now",
      "name": "Weather API (pay-per-call)",
      "description": "A themed but fully compliant data endpoint, gated by a real settlement.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/weather-now",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/weather-now",
      "documentation": "https://grug402.dev/scenario/weather-now.md"
    },
    {
      "id": "fx-rate",
      "name": "FX rate API (pay-per-call)",
      "description": "A themed compliant endpoint that returns an exchange rate after settlement.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/fx-rate",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/fx-rate",
      "documentation": "https://grug402.dev/scenario/fx-rate.md"
    },
    {
      "id": "haiku-generator",
      "name": "Haiku generator (POST, paid)",
      "description": "A compliant POST endpoint that returns generated content after settlement.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/haiku-generator",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": {
        "topic": "x402"
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/haiku-generator -H 'Content-Type: application/json' -d '{\"topic\":\"x402\"}'",
      "documentation": "https://grug402.dev/scenario/haiku-generator.md"
    },
    {
      "id": "status-401",
      "name": "Returns 401 instead of 402",
      "description": "Gates the resource with 401 Unauthorized instead of 402 Payment Required.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/status-401",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/status-401",
      "documentation": "https://grug402.dev/scenario/status-401.md"
    },
    {
      "id": "status-403",
      "name": "Returns 403 instead of 402",
      "description": "Gates the resource with 403 Forbidden instead of 402.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/status-403",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/status-403",
      "documentation": "https://grug402.dev/scenario/status-403.md"
    },
    {
      "id": "status-400",
      "name": "Returns 400 instead of 402",
      "description": "Gates the resource with 400 Bad Request instead of 402.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/status-400",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/status-400",
      "documentation": "https://grug402.dev/scenario/status-400.md"
    },
    {
      "id": "status-200-leak",
      "name": "Returns 200 with content (no gate)",
      "description": "Serves the protected content for free with a 200 and no payment gate.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/status-200-leak",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/status-200-leak",
      "documentation": "https://grug402.dev/scenario/status-200-leak.md"
    },
    {
      "id": "missing-payment-required",
      "name": "402 without PAYMENT-REQUIRED header",
      "description": "Returns a 402 but omits the PAYMENT-REQUIRED header entirely.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/missing-payment-required",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/missing-payment-required",
      "documentation": "https://grug402.dev/scenario/missing-payment-required.md"
    },
    {
      "id": "header-raw-json",
      "name": "PAYMENT-REQUIRED is raw JSON (not base64)",
      "description": "Sends the PAYMENT-REQUIRED requirements as raw JSON instead of base64.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/header-raw-json",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/header-raw-json",
      "documentation": "https://grug402.dev/scenario/header-raw-json.md"
    },
    {
      "id": "header-bad-base64",
      "name": "PAYMENT-REQUIRED is invalid base64",
      "description": "The PAYMENT-REQUIRED header value is not valid base64.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/header-bad-base64",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/header-bad-base64",
      "documentation": "https://grug402.dev/scenario/header-bad-base64.md"
    },
    {
      "id": "header-base64-not-json",
      "name": "Base64 decodes to non-JSON",
      "description": "The PAYMENT-REQUIRED header is valid base64 but decodes to bytes that are not JSON.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/header-base64-not-json",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/header-base64-not-json",
      "documentation": "https://grug402.dev/scenario/header-base64-not-json.md"
    },
    {
      "id": "amount-as-number",
      "name": "Atomic amount as a JSON number",
      "description": "Emits the atomic amount as a JSON number instead of a string.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/amount-as-number",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/amount-as-number",
      "documentation": "https://grug402.dev/scenario/amount-as-number.md"
    },
    {
      "id": "missing-x402-version",
      "name": "Missing x402Version",
      "description": "The PAYMENT-REQUIRED body omits the x402Version field.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/missing-x402-version",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/missing-x402-version",
      "documentation": "https://grug402.dev/scenario/missing-x402-version.md"
    },
    {
      "id": "legacy-v1-headers",
      "name": "Legacy v1 headers (X-PAYMENT)",
      "description": "Emits the legacy v1 X-PAYMENT header instead of the v2 PAYMENT-REQUIRED challenge.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/legacy-v1-headers",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/legacy-v1-headers",
      "documentation": "https://grug402.dev/scenario/legacy-v1-headers.md"
    },
    {
      "id": "empty-accepts",
      "name": "Empty accepts array",
      "description": "The PAYMENT-REQUIRED challenge advertises an empty accepts array.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/empty-accepts",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/empty-accepts",
      "documentation": "https://grug402.dev/scenario/empty-accepts.md"
    },
    {
      "id": "unsupported-scheme",
      "name": "Unsupported payment scheme",
      "description": "Advertises a payment scheme the facilitator does not support.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/unsupported-scheme",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/unsupported-scheme",
      "documentation": "https://grug402.dev/scenario/unsupported-scheme.md"
    },
    {
      "id": "wrong-network-mainnet",
      "name": "Advertises mainnet, settles on testnet",
      "description": "Advertises a mainnet network id while the facilitator runs on testnet.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/wrong-network-mainnet",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Network / Chain",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/wrong-network-mainnet",
      "documentation": "https://grug402.dev/scenario/wrong-network-mainnet.md"
    },
    {
      "id": "wrong-token",
      "name": "Wrong token contract address",
      "description": "Advertises an asset contract address that is not SBC.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/wrong-token",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Token Issuer",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/wrong-token",
      "documentation": "https://grug402.dev/scenario/wrong-token.md"
    },
    {
      "id": "wrong-decimals",
      "name": "Wrong decimals (18 instead of 6)",
      "description": "Expresses the amount with 18 decimals for a token that has 6.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/wrong-decimals",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Token Issuer",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/wrong-decimals",
      "documentation": "https://grug402.dev/scenario/wrong-decimals.md"
    },
    {
      "id": "wrong-payto",
      "name": "Wrong payTo address",
      "description": "Advertises a payTo address that is not the merchant's wallet.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/wrong-payto",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/wrong-payto",
      "documentation": "https://grug402.dev/scenario/wrong-payto.md"
    },
    {
      "id": "missing-resource",
      "name": "Missing resource URL",
      "description": "The PAYMENT-REQUIRED challenge omits the resource object.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/missing-resource",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/missing-resource",
      "documentation": "https://grug402.dev/scenario/missing-resource.md"
    },
    {
      "id": "expired-quote",
      "name": "Already-expired quote (timeout 0)",
      "description": "Sets maxTimeoutSeconds to 0, so the quote is dead on arrival.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/expired-quote",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/expired-quote",
      "documentation": "https://grug402.dev/scenario/expired-quote.md"
    },
    {
      "id": "overlong-validity",
      "name": "Overlong validity window (1 year)",
      "description": "Sets maxTimeoutSeconds to a full year, widening the replay window.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/overlong-validity",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/overlong-validity",
      "documentation": "https://grug402.dev/scenario/overlong-validity.md"
    },
    {
      "id": "changing-price",
      "name": "Price changes every request",
      "description": "Increases the advertised amount on every request.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/changing-price",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/changing-price",
      "documentation": "https://grug402.dev/scenario/changing-price.md"
    },
    {
      "id": "dollar-string-price",
      "name": "Dollar-string price ($0.0001)",
      "description": "Advertises the amount as a human dollar string instead of atomic units.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/dollar-string-price",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/dollar-string-price",
      "documentation": "https://grug402.dev/scenario/dollar-string-price.md"
    },
    {
      "id": "sensitive-metadata",
      "name": "Sensitive data in metadata",
      "description": "Leaks fake PII into the resource description before any payment.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/sensitive-metadata",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/sensitive-metadata",
      "documentation": "https://grug402.dev/scenario/sensitive-metadata.md"
    },
    {
      "id": "skip-verify",
      "name": "Skips /verify, settles directly",
      "description": "Goes straight to /settle without a /verify pre-check.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/skip-verify",
      "category": "Verification Gaps",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/skip-verify",
      "documentation": "https://grug402.dev/scenario/skip-verify.md"
    },
    {
      "id": "verify-only-no-settle",
      "name": "Grants on /verify, never settles",
      "description": "Grants access once /verify succeeds and never calls /settle.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/verify-only-no-settle",
      "category": "Verification Gaps",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/verify-only-no-settle",
      "documentation": "https://grug402.dev/scenario/verify-only-no-settle.md"
    },
    {
      "id": "no-facilitator-check",
      "name": "No verification at all",
      "description": "Grants access on the mere presence of a PAYMENT-SIGNATURE header.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/no-facilitator-check",
      "category": "Verification Gaps",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/no-facilitator-check",
      "documentation": "https://grug402.dev/scenario/no-facilitator-check.md"
    },
    {
      "id": "trust-client-response",
      "name": "Trusts client-supplied result",
      "description": "Believes a client-asserted payment result without checking the facilitator.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/trust-client-response",
      "category": "Verification Gaps",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/trust-client-response",
      "documentation": "https://grug402.dev/scenario/trust-client-response.md"
    },
    {
      "id": "fail-open-timeout",
      "name": "Fails open when the facilitator is down",
      "description": "Serves the resource anyway when the facilitator is unreachable.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/fail-open-timeout",
      "category": "Verification Gaps",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/fail-open-timeout",
      "documentation": "https://grug402.dev/scenario/fail-open-timeout.md"
    },
    {
      "id": "replay-after-success",
      "name": "Replay after success (no binding)",
      "description": "Once a payment settles, the same payload re-serves the resource forever with no re-settle.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/replay-after-success",
      "category": "Replay / Idempotency / Race",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/replay-after-success",
      "documentation": "https://grug402.dev/scenario/replay-after-success.md"
    },
    {
      "id": "serve-before-settle",
      "name": "Serves before settlement (verify/settle gap)",
      "description": "Returns 200 immediately and settles in the background.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/serve-before-settle",
      "category": "Replay / Idempotency / Race",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/serve-before-settle",
      "documentation": "https://grug402.dev/scenario/serve-before-settle.md"
    },
    {
      "id": "cors-hides-headers",
      "name": "CORS hides payment headers",
      "description": "Does not expose the PAYMENT-REQUIRED and PAYMENT-RESPONSE headers via CORS.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/cors-hides-headers",
      "category": "Web / HTTP Layer",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/cors-hides-headers",
      "documentation": "https://grug402.dev/scenario/cors-hides-headers.md"
    },
    {
      "id": "head-leak",
      "name": "HEAD reveals paid metadata",
      "description": "A HEAD request returns 200 with a leak header and no payment.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/head-leak",
      "category": "Web / HTTP Layer",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/head-leak",
      "documentation": "https://grug402.dev/scenario/head-leak.md"
    },
    {
      "id": "upto-correct",
      "name": "upto: settles actual usage",
      "description": "Authorizes a maximum but settles only the actual usage (~60%).",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/upto-correct",
      "category": "Schemes (exact / upto / batch)",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": {
        "tokens": 600
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/upto-correct -H 'Content-Type: application/json' -d '{\"tokens\":600}'",
      "documentation": "https://grug402.dev/scenario/upto-correct.md"
    },
    {
      "id": "upto-charges-max",
      "name": "upto: always charges the max",
      "description": "Settles the full authorized max even when usage is lower.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/upto-charges-max",
      "category": "Schemes (exact / upto / batch)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "tokens": 10
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/upto-charges-max -H 'Content-Type: application/json' -d '{\"tokens\":10}'",
      "documentation": "https://grug402.dev/scenario/upto-charges-max.md"
    },
    {
      "id": "upto-override-exceeds-max",
      "name": "upto: override exceeds the max",
      "description": "Sets the settlement amount above the authorized maximum.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/upto-override-exceeds-max",
      "category": "Schemes (exact / upto / batch)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "tokens": 5000
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/upto-override-exceeds-max -H 'Content-Type: application/json' -d '{\"tokens\":5000}'",
      "documentation": "https://grug402.dev/scenario/upto-override-exceeds-max.md"
    },
    {
      "id": "upto-zero-free",
      "name": "upto: settles zero (free)",
      "description": "A settlement override of 0 gives the service away by accident.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/upto-zero-free",
      "category": "Schemes (exact / upto / batch)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "tokens": 600
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/upto-zero-free -H 'Content-Type: application/json' -d '{\"tokens\":600}'",
      "documentation": "https://grug402.dev/scenario/upto-zero-free.md"
    },
    {
      "id": "dead-facilitator",
      "name": "Dead facilitator URL",
      "description": "Points /verify and /settle at a facilitator host that does not exist.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/dead-facilitator",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Facilitator",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/dead-facilitator",
      "documentation": "https://grug402.dev/scenario/dead-facilitator.md"
    },
    {
      "id": "unsupported-network-facilitator",
      "name": "Facilitator can't support the network",
      "description": "Advertises a network the facilitator does not handle.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/unsupported-network-facilitator",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Facilitator",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/unsupported-network-facilitator",
      "documentation": "https://grug402.dev/scenario/unsupported-network-facilitator.md"
    },
    {
      "id": "facilitator-rate-limited",
      "name": "Facilitator rate-limits /verify (429 → misclassified 402)",
      "description": "The facilitator returns 429 during /verify; the merchant misclassifies it as a payment failure and returns 402 instead of 503 + Retry-After.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-rate-limited",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-rate-limited",
      "documentation": "https://grug402.dev/scenario/facilitator-rate-limited.md"
    },
    {
      "id": "facilitator-soft-down-503",
      "name": "Facilitator temporarily unavailable (503)",
      "description": "The facilitator is reachable but returns 503 Service Unavailable — it is not a dead URL, just temporarily down.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-soft-down-503",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Facilitator",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-soft-down-503",
      "documentation": "https://grug402.dev/scenario/facilitator-soft-down-503.md"
    },
    {
      "id": "facilitator-settle-500",
      "name": "Settle returns 500 (ambiguous paid state)",
      "description": "/verify succeeds but /settle returns 500 Internal Server Error, leaving it unknown whether the payment landed on-chain.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-settle-500",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Facilitator",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-settle-500",
      "documentation": "https://grug402.dev/scenario/facilitator-settle-500.md"
    },
    {
      "id": "facilitator-missing-txhash",
      "name": "Settle succeeds without txHash",
      "description": "/settle reports success: true but omits every transaction-hash field; the PAYMENT-RESPONSE receipt carries no on-chain proof.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-missing-txhash",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Facilitator",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-missing-txhash",
      "documentation": "https://grug402.dev/scenario/facilitator-missing-txhash.md"
    },
    {
      "id": "facilitator-non-json-leaks",
      "name": "Non-JSON facilitator response leaked in 402",
      "description": "The facilitator returns an HTML maintenance page instead of JSON; the merchant forwards the raw HTML in the 402 response body, leaking internal infrastructure details.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-non-json-leaks",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-non-json-leaks",
      "documentation": "https://grug402.dev/scenario/facilitator-non-json-leaks.md"
    },
    {
      "id": "facilitator-schema-drift",
      "name": "Schema drift: valid vs isValid (loose check grants)",
      "description": "The facilitator returns { valid: true } instead of the spec's { isValid: true }; the merchant's loose field-name check grants access on the wrong key.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-schema-drift",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-schema-drift",
      "documentation": "https://grug402.dev/scenario/facilitator-schema-drift.md"
    },
    {
      "id": "facilitator-no-verify-timeout",
      "name": "No timeout on /verify (hangs indefinitely)",
      "description": "The merchant's /verify fetch has no AbortController or timeout; when the facilitator is slow the handler ties up the connection forever.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-no-verify-timeout",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-no-verify-timeout",
      "documentation": "https://grug402.dev/scenario/facilitator-no-verify-timeout.md"
    },
    {
      "id": "facilitator-wrong-payto-at-settle",
      "name": "Wrong payTo used at settle time",
      "description": "The merchant calls /settle with a payTo address different from the one advertised in the challenge; the facilitator rejects it because the payment was signed for a different recipient.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-wrong-payto-at-settle",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-wrong-payto-at-settle",
      "documentation": "https://grug402.dev/scenario/facilitator-wrong-payto-at-settle.md"
    },
    {
      "id": "facilitator-already-settled-grants",
      "name": "Grants access despite settle failure",
      "description": "When /settle returns a failure (e.g., already used), the merchant grants access anyway — a replay attacker can reuse any previously-rejected permit for free.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-already-settled-grants",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-already-settled-grants",
      "documentation": "https://grug402.dev/scenario/facilitator-already-settled-grants.md"
    },
    {
      "id": "facilitator-settle-concurrent-race",
      "name": "Concurrent settle race (second racer gets 402)",
      "description": "Two concurrent requests carry the same PAYMENT-SIGNATURE; without a per-permit lock the merchant lets both race to /settle. The facilitator accepts only one — the second racer is charged but gets a confusing 402.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/facilitator-settle-concurrent-race",
      "category": "Facilitator Dependency",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/facilitator-settle-concurrent-race",
      "documentation": "https://grug402.dev/scenario/facilitator-settle-concurrent-race.md"
    },
    {
      "id": "settle-then-fail",
      "name": "Settles, then the handler fails",
      "description": "The payment settles on-chain, then the handler returns 500.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/settle-then-fail",
      "category": "Paid-but-Denied / Unpaid-Service",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/settle-then-fail",
      "documentation": "https://grug402.dev/scenario/settle-then-fail.md"
    },
    {
      "id": "missing-payment-response",
      "name": "Success without PAYMENT-RESPONSE",
      "description": "Settles and returns 200 but omits the PAYMENT-RESPONSE header.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/missing-payment-response",
      "category": "Paid-but-Denied / Unpaid-Service",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/missing-payment-response",
      "documentation": "https://grug402.dev/scenario/missing-payment-response.md"
    },
    {
      "id": "joke-api",
      "name": "Joke API (pay-per-call)",
      "description": "A compliant GET that returns a joke after a real settlement.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/joke-api",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/joke-api",
      "documentation": "https://grug402.dev/scenario/joke-api.md"
    },
    {
      "id": "dictionary-api",
      "name": "Dictionary API (pay-per-call)",
      "description": "A compliant GET that returns a definition after settlement.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/dictionary-api",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/dictionary-api",
      "documentation": "https://grug402.dev/scenario/dictionary-api.md"
    },
    {
      "id": "quote-api",
      "name": "Quote API (pay-per-call)",
      "description": "A compliant GET that returns a quote after settlement.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/quote-api",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/quote-api",
      "documentation": "https://grug402.dev/scenario/quote-api.md"
    },
    {
      "id": "wrong-transfer-method",
      "name": "Wrong asset transfer method",
      "description": "Sets extra.assetTransferMethod to something other than permit2, so the facilitator can't move funds.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/wrong-transfer-method",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/wrong-transfer-method",
      "documentation": "https://grug402.dev/scenario/wrong-transfer-method.md"
    },
    {
      "id": "zero-price-gated",
      "name": "Advertises a zero price but still gates",
      "description": "Advertises an amount of 0 yet still demands a payment header.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/zero-price-gated",
      "category": "402 / Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "0",
        "sbc": "0"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/zero-price-gated",
      "documentation": "https://grug402.dev/scenario/zero-price-gated.md"
    },
    {
      "id": "batch-settlement-correct",
      "name": "batch-settlement: deposit, then reconcile usage",
      "description": "Advertises the batch-settlement scheme: the client authorizes a 5x deposit, and the server settles only the actual (much lower) usage.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/batch-settlement-correct",
      "category": "Schemes (exact / upto / batch)",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": {
        "calls": 3
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/batch-settlement-correct -H 'Content-Type: application/json' -d '{\"calls\":3}'",
      "documentation": "https://grug402.dev/scenario/batch-settlement-correct.md"
    },
    {
      "id": "batch-settlement-overcharge-deposit",
      "name": "batch-settlement: settles beyond the deposit",
      "description": "Settles more than the deposit the client authorized under the batch scheme.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/batch-settlement-overcharge-deposit",
      "category": "Schemes (exact / upto / batch)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "calls": 3
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/batch-settlement-overcharge-deposit -H 'Content-Type: application/json' -d '{\"calls\":3}'",
      "documentation": "https://grug402.dev/scenario/batch-settlement-overcharge-deposit.md"
    },
    {
      "id": "batch-settlement-never-reconciles",
      "name": "batch-settlement: never reconciles down",
      "description": "Holds the full authorized deposit and never settles the lower real usage.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/batch-settlement-never-reconciles",
      "category": "Schemes (exact / upto / batch)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "calls": 1
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/batch-settlement-never-reconciles -H 'Content-Type: application/json' -d '{\"calls\":1}'",
      "documentation": "https://grug402.dev/scenario/batch-settlement-never-reconciles.md"
    },
    {
      "id": "client-scheme-mismatch",
      "name": "exact: canonical scheme identifier (client sends the wrong one)",
      "description": "A fully compliant exact endpoint. The scheme identifier in the payment payload MUST be the canonical \"exact\" — a client that instead sends a non-canonical name like \"exact-evm\" is rejected by the facilitator with invalid_exact_evm_scheme even though its signature recovers a valid payer.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/client-scheme-mismatch",
      "category": "Schemes (exact / upto / batch)",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Client",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/client-scheme-mismatch",
      "documentation": "https://grug402.dev/scenario/client-scheme-mismatch.md"
    },
    {
      "id": "multi-network-accepts",
      "name": "Genuine multi-entry accepts (match by network)",
      "description": "Advertises two EVM entries — Base Sepolia first, Radius testnet second — so the client must match by network instead of taking accepts[0].",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/multi-network-accepts",
      "category": "Multi-Network Selection (MCP)",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/multi-network-accepts",
      "documentation": "https://grug402.dev/scenario/multi-network-accepts.md"
    },
    {
      "id": "svm-requirement",
      "name": "SVM-only requirement (solana:*)",
      "description": "Advertises only a Solana (solana:*) requirement, which an EVM testnet client cannot satisfy.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/svm-requirement",
      "category": "Multi-Network Selection (MCP)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Network / Chain",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/svm-requirement",
      "documentation": "https://grug402.dev/scenario/svm-requirement.md"
    },
    {
      "id": "cross-vm-accepts",
      "name": "Cross-VM accepts (EVM and SVM together)",
      "description": "Advertises a Solana entry and a Radius testnet EVM entry together, forcing the client to select the VM and network it can settle on.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/cross-vm-accepts",
      "category": "Multi-Network Selection (MCP)",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/cross-vm-accepts",
      "documentation": "https://grug402.dev/scenario/cross-vm-accepts.md"
    },
    {
      "id": "overpriced-vs-cap",
      "name": "Overpriced vs per-call cap",
      "description": "Advertises a price far above a sane per-call cap that an MCP policy gate would enforce.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/overpriced-vs-cap",
      "category": "Client Policy Gate (MCP)",
      "compliant": false,
      "price": {
        "atomic": "1000000",
        "sbc": "1"
      },
      "responsibleParty": "Client",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/overpriced-vs-cap",
      "documentation": "https://grug402.dev/scenario/overpriced-vs-cap.md"
    },
    {
      "id": "resource-host-mismatch",
      "name": "Resource host mismatch",
      "description": "The advertised resource origin differs from the server actually being called.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/resource-host-mismatch",
      "category": "Client Policy Gate (MCP)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Client",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/resource-host-mismatch",
      "documentation": "https://grug402.dev/scenario/resource-host-mismatch.md"
    },
    {
      "id": "tool-operation-mismatch",
      "name": "Tool / operation mismatch",
      "description": "The requirement authorizes a different, more privileged tool than the one being requested.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/tool-operation-mismatch",
      "category": "Client Policy Gate (MCP)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Client",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/tool-operation-mismatch",
      "documentation": "https://grug402.dev/scenario/tool-operation-mismatch.md"
    },
    {
      "id": "session-price-escalation",
      "name": "Session price escalation",
      "description": "The price climbs call over call (1x, 2x, 3x...) to defeat a per-session spend limit.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/session-price-escalation",
      "category": "Client Policy Gate (MCP)",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Client",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/session-price-escalation",
      "documentation": "https://grug402.dev/scenario/session-price-escalation.md"
    },
    {
      "id": "edge-correct-headers",
      "name": "Correct edge cache headers (compliant)",
      "description": "The 402 challenge is no-store; the paid 200 is private, no-store with Vary: PAYMENT-SIGNATURE — and it still does a real verify and settle.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-correct-headers",
      "category": "Edge / CDN Controls",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-correct-headers",
      "documentation": "https://grug402.dev/scenario/edge-correct-headers.md"
    },
    {
      "id": "edge-cacheable-challenge",
      "name": "Cacheable 402 challenge",
      "description": "Sends the 402 with Cache-Control: public, max-age, so a shared cache stores and replays a stale challenge.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-cacheable-challenge",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-cacheable-challenge",
      "documentation": "https://grug402.dev/scenario/edge-cacheable-challenge.md"
    },
    {
      "id": "edge-cacheable-paid",
      "name": "Cacheable paid response (shared cache)",
      "description": "Sends the paid 200 publicly cacheable, so a shared edge serves paid content to unpaid clients on the same URL.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-cacheable-paid",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-cacheable-paid",
      "documentation": "https://grug402.dev/scenario/edge-cacheable-paid.md"
    },
    {
      "id": "edge-missing-vary",
      "name": "Missing Vary on payment",
      "description": "Caches the paid 200 but ignores the payment header in the cache key (no Vary), so one payer's response is served to another.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-missing-vary",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-missing-vary",
      "documentation": "https://grug402.dev/scenario/edge-missing-vary.md"
    },
    {
      "id": "edge-swr-leak",
      "name": "stale-while-revalidate leak",
      "description": "A stale-while-revalidate directive lets the edge serve cached protected content without payment during revalidation.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-swr-leak",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-swr-leak",
      "documentation": "https://grug402.dev/scenario/edge-swr-leak.md"
    },
    {
      "id": "edge-oversized-header",
      "name": "Oversized challenge header",
      "description": "Pads the PAYMENT-REQUIRED header beyond common proxy header-size caps, so an edge truncates, drops, or 431s it.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-oversized-header",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-oversized-header",
      "documentation": "https://grug402.dev/scenario/edge-oversized-header.md"
    },
    {
      "id": "edge-conditional-304",
      "name": "Conditional 304 replay",
      "description": "The paid resource carries an ETag, and a later conditional request is answered 304 from cache without re-payment.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-conditional-304",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-conditional-304",
      "documentation": "https://grug402.dev/scenario/edge-conditional-304.md"
    },
    {
      "id": "edge-rate-limited",
      "name": "Rate-limited before payment",
      "description": "Returns 429 (short Retry-After, no challenge) before the 402, so a willing payer can't tell a rate limit from a paywall.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-rate-limited",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-rate-limited",
      "documentation": "https://grug402.dev/scenario/edge-rate-limited.md"
    },
    {
      "id": "edge-waf-blocks-payment",
      "name": "WAF blocks the payment payload",
      "description": "The large base64 PAYMENT-SIGNATURE trips a managed WAF rule and is rejected with 403 before the payment logic runs.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-waf-blocks-payment",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-waf-blocks-payment",
      "documentation": "https://grug402.dev/scenario/edge-waf-blocks-payment.md"
    },
    {
      "id": "edge-body-too-large",
      "name": "Body too large (413)",
      "description": "A paid POST is rejected by a small edge body cap with 413 before reaching the payment logic.",
      "method": "POST",
      "url": "http://127.0.0.1:8080/api/x402/edge-body-too-large",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": {
        "query": "radius"
      },
      "invoke": "radius-cli --network testnet wallet x402 post http://127.0.0.1:8080/api/x402/edge-body-too-large -H 'Content-Type: application/json' -d '{\"query\":\"radius\"}'",
      "documentation": "https://grug402.dev/scenario/edge-body-too-large.md"
    },
    {
      "id": "edge-timeout-settle",
      "name": "Edge timeout during settle",
      "description": "Settlement is slow and the edge returns 504 while the charge may have completed on-chain — an ambiguous paid state.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/x402/edge-timeout-settle",
      "category": "Edge / CDN Controls",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/x402/edge-timeout-settle",
      "documentation": "https://grug402.dev/scenario/edge-timeout-settle.md"
    },
    {
      "id": "mpp-push-happy",
      "name": "MPP push happy path (EIP-3009)",
      "description": "Compliant MPP push flow: server issues WWW-Authenticate: Payment challenge, client submits EIP-3009 authorization credential, server validates structure and returns 200 with Payment-Receipt.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-push-happy",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-push-happy",
      "documentation": "https://grug402.dev/scenario/mpp-push-happy.md"
    },
    {
      "id": "mpp-permit-happy",
      "name": "MPP permit happy path (EIP-2612)",
      "description": "Compliant MPP permit flow: challenge requests an EIP-2612 permit credential. Client submits signed permit in Authorization: Payment, server validates and returns 200.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-permit-happy",
      "category": "Happy Path",
      "compliant": true,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": null,
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-permit-happy",
      "documentation": "https://grug402.dev/scenario/mpp-permit-happy.md"
    },
    {
      "id": "mpp-wrong-status",
      "name": "MPP challenge returns 401 instead of 402",
      "description": "Issues the MPP payment challenge with HTTP 401 Unauthorized instead of the required 402 Payment Required.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-wrong-status",
      "category": "Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-wrong-status",
      "documentation": "https://grug402.dev/scenario/mpp-wrong-status.md"
    },
    {
      "id": "mpp-missing-authenticate",
      "name": "MPP 402 without WWW-Authenticate",
      "description": "Returns 402 but omits the WWW-Authenticate: Payment header entirely. The client receives a payment signal but no challenge to respond to.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-missing-authenticate",
      "category": "Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-missing-authenticate",
      "documentation": "https://grug402.dev/scenario/mpp-missing-authenticate.md"
    },
    {
      "id": "mpp-raw-json-authenticate",
      "name": "WWW-Authenticate challenge is raw JSON (not Payment scheme)",
      "description": "Emits WWW-Authenticate with the challenge embedded as raw JSON instead of the \"Payment method=\\\"evm/charge\\\" challenge=\\\"<base64>\\\"\" format.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-raw-json-authenticate",
      "category": "Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-raw-json-authenticate",
      "documentation": "https://grug402.dev/scenario/mpp-raw-json-authenticate.md"
    },
    {
      "id": "mpp-bearer-authenticate",
      "name": "WWW-Authenticate uses Bearer instead of Payment scheme",
      "description": "Returns WWW-Authenticate: Bearer realm=\"...\" — the OAuth scheme — instead of the Payment scheme. MPP clients have no credential type to submit.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-bearer-authenticate",
      "category": "Challenge Construction",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-bearer-authenticate",
      "documentation": "https://grug402.dev/scenario/mpp-bearer-authenticate.md"
    },
    {
      "id": "mpp-bad-credential",
      "name": "MPP server rejects every credential",
      "description": "The server always returns 401 on any Authorization: Payment credential, even structurally valid ones. No credential can satisfy this endpoint.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-bad-credential",
      "category": "Credential Validation",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-bad-credential",
      "documentation": "https://grug402.dev/scenario/mpp-bad-credential.md"
    },
    {
      "id": "mpp-skip-validation",
      "name": "MPP grants on credential presence (no validation)",
      "description": "Grants access on the mere presence of an Authorization: Payment header — any credential value, even random bytes, unlocks the resource.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-skip-validation",
      "category": "Credential Validation",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-skip-validation",
      "documentation": "https://grug402.dev/scenario/mpp-skip-validation.md"
    },
    {
      "id": "mpp-paid-denied",
      "name": "MPP paid-but-denied (403 after valid credential)",
      "description": "Accepts a structurally valid push credential but returns 403 Forbidden with a Payment-Receipt failure header. The payment credential was consumed without delivering the resource.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-paid-denied",
      "category": "Paid-but-Denied",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-paid-denied",
      "documentation": "https://grug402.dev/scenario/mpp-paid-denied.md"
    },
    {
      "id": "mpp-replay-accepted",
      "name": "MPP replay accepted (no idempotency check)",
      "description": "The server accepts a previously-used push credential without checking for replay. A single credential can be reused indefinitely to obtain the resource.",
      "method": "GET",
      "url": "http://127.0.0.1:8080/api/mpp/mpp-replay-accepted",
      "category": "Credential Validation",
      "compliant": false,
      "price": {
        "atomic": "100",
        "sbc": "0.0001"
      },
      "responsibleParty": "Merchant",
      "sampleBody": null,
      "invoke": "radius-cli --network testnet wallet x402 get http://127.0.0.1:8080/api/mpp/mpp-replay-accepted",
      "documentation": "https://grug402.dev/scenario/mpp-replay-accepted.md"
    }
  ]
}