# x402 Marketplace

> A reference catalog of HTTP 402 ("Payment Required") API endpoints on the Radius testnet (eip155:72344, chain 72344). It exists so developers and AI agents can test an x402 payment client against 21 fully-compliant endpoints and 76 intentional protocol quirks — each quirk deviates from the spec in exactly one documented way.

## What is x402

x402 is the HTTP 402 payment protocol: a server responds `402 Payment Required` with a base64 `PAYMENT-REQUIRED` header describing what to pay; the client signs a payment authorization and retries; the server verifies and settles it with a facilitator, then returns the paid resource plus a `PAYMENT-RESPONSE` header. This marketplace settles real payments against the Radius testnet facilitator.

This service's auth and payment protocol is x402, not OAuth 2.0 or OpenID Connect. There are no OAuth/OIDC endpoints; probes for OAuth/OIDC discovery documents (for example `/.well-known/openid-configuration`) return an `application/problem+json` error that points to the canonical discovery surfaces (`/.well-known/api-catalog` and `/.well-known/ucp`).

## How to use

The supported client is `radius-cli`. Call any endpoint with:

```
radius-cli --network testnet wallet x402 get <url>
radius-cli --network testnet wallet x402 post <url> -H 'Content-Type: application/json' -d '<json>'
```

- Network: eip155:72344 (chain 72344)
- Asset (SBC, 6 decimals): 0x33ad9e4BD16B69B5BFdED37D8B5D9fF9aba014Fb
- Facilitator: https://facilitator.testnet.radiustech.xyz
- Pay to: 0x05354779F8E5397624928a461b97b190CAFcc23B
- Builder code: `grug402` (ERC-8021 — declared in every 402 challenge under `https://x402.org/extensions/builder-code`)
- Built by people at Radius (https://radiustech.xyz) — Built for Radius workshop. Stablecoin-native settlement infrastructure for the agentic internet.

## Discovery

- [Agent Readiness page](https://grug402.dev/agent-ready): human-facing index of every machine-readable surface
- [OpenAPI document](https://grug402.dev/openapi.yaml): enriched OpenAPI 3.1 describing every endpoint (service-desc; importable into Kong/decK and any OpenAPI tool)
- [Postman collection](https://grug402.dev/x402.postman_collection.json): Postman Collection v2.1 of every scenario, grouped by category
- [Insomnia export](https://grug402.dev/x402.insomnia.json): Insomnia v4 export of every scenario, grouped by category
- [API catalog](https://grug402.dev/.well-known/api-catalog): RFC 9727 catalog of API descriptions
- [Agent skills](https://grug402.dev/.well-known/agent-skills): machine-readable actions an agent can perform here
- [Agent Skills index](https://grug402.dev/.well-known/agent-skills/index.json): RFC v0.2.0 agent-skills discovery index listing the x402-payments skill
- [UCP commerce profile](https://grug402.dev/.well-known/ucp): Universal Commerce Protocol profile so agents can discover payments
- [ACP discovery document](https://grug402.dev/.well-known/acp.json): Agentic Commerce Protocol discovery document so agents can find this commerce API before checkout
- [ARD ai-catalog](https://grug402.dev/.well-known/ai-catalog.json): Agentic Resource Discovery catalog — structured index of every API, MCP server, skill, and documentation surface on this site for ARD registries and agent crawlers
- [API Tools](https://grug402.dev/api-tools): interactive browser for calling any x402 endpoint directly from the UI
- [humans.txt](https://grug402.dev/humans.txt): the people who built this site (Radius)
- [Sitemap](https://grug402.dev/sitemap.xml): every page
- [Homepage as markdown](https://grug402.dev/index.md): catalog without the JS shell
- [What is x402?](https://grug402.dev/what-is-x402): plain-English explainer of the protocol and FAQ
- [x402 Dev Tools](https://grug402.dev/dev-tools): CLI, pay script, Postman/Insomnia/OpenAPI, and MCP server in one hub
- [Test Your x402 Client](https://grug402.dev/test-your-client): step-by-step guide to testing a client against the sandbox
- [x402 Example Endpoints](https://grug402.dev/examples): every scenario grouped by bug class
- [x402 Glossary](https://grug402.dev/glossary): protocol terms and quirk bug classes defined

## Ecosystem / references

The standards, chains, infra, assets, and tooling behind grug402:

- [x402](https://x402.org): the open HTTP-402 payment protocol this site is a reference for
- [MPP (Machine Payments Protocol)](https://mpp.dev): related agent-to-agent payments protocol
- [Radius](https://radiustech.xyz): testnet, faucet, and facilitator infrastructure; host of the workshop and builder of grug402
- [Base](https://base.org): Coinbase's L2 chain in the x402 ecosystem; a supported settlement network
- [Stablecoin (SBC)](https://stablecoin.xyz): the stablecoin used for settlement on the Radius testnet
- [USDC](https://usdc.com): Circle's stablecoin, the canonical x402 settlement asset on mainnet chains
- [Cloudflare](https://cloudflare.com): AI Crawl Control / Content Signals used in this site's robots.txt
- [isitagentready.com](https://isitagentready.com): the agent-readiness scanner that validates this site's discovery surfaces

## Happy Path

- [Canonical GET (real verify and settle)](https://grug402.dev/scenario/compliant-get.md) (GET, compliant): 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.
- [Canonical POST with body](https://grug402.dev/scenario/compliant-post.md) (POST, compliant): The same compliant flow as the canonical GET, for a POST that carries a JSON body.
- [Body-primary GET challenge](https://grug402.dev/scenario/compliant-body-get.md) (GET, compliant): 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.
- [Body-primary POST challenge](https://grug402.dev/scenario/compliant-body-post.md) (POST, compliant): The body-primary happy path for a POST endpoint. Challenge is delivered in the JSON response body only, no PAYMENT-REQUIRED header.
- [Minimal price (1 atomic unit)](https://grug402.dev/scenario/cheapest.md) (GET, compliant): A compliant endpoint priced at the smallest atomic unit, 0.000001 SBC.
- [Single testnet accepts entry](https://grug402.dev/scenario/multi-accepts.md) (GET, compliant): Advertises one testnet requirement that a correct client selects by matching its network.
- [Correct idempotency (cached retry)](https://grug402.dev/scenario/idempotent-retry.md) (GET, compliant): Caches the result by payment id, so retrying the same payment returns the cached 200 without charging twice.
- [Weather API (pay-per-call)](https://grug402.dev/scenario/weather-now.md) (GET, compliant): A themed but fully compliant data endpoint, gated by a real settlement.
- [FX rate API (pay-per-call)](https://grug402.dev/scenario/fx-rate.md) (GET, compliant): A themed compliant endpoint that returns an exchange rate after settlement.
- [Haiku generator (POST, paid)](https://grug402.dev/scenario/haiku-generator.md) (POST, compliant): A compliant POST endpoint that returns generated content after settlement.
- [Joke API (pay-per-call)](https://grug402.dev/scenario/joke-api.md) (GET, compliant): A compliant GET that returns a joke after a real settlement.
- [Dictionary API (pay-per-call)](https://grug402.dev/scenario/dictionary-api.md) (GET, compliant): A compliant GET that returns a definition after settlement.
- [Quote API (pay-per-call)](https://grug402.dev/scenario/quote-api.md) (GET, compliant): A compliant GET that returns a quote after settlement.
- [MPP push happy path (EIP-3009)](https://grug402.dev/scenario/mpp-push-happy.md) (GET, compliant): 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.
- [MPP permit happy path (EIP-2612)](https://grug402.dev/scenario/mpp-permit-happy.md) (GET, compliant): Compliant MPP permit flow: challenge requests an EIP-2612 permit credential. Client submits signed permit in Authorization: Payment, server validates and returns 200.

## 402 / Challenge Construction

- [Returns 401 instead of 402](https://grug402.dev/scenario/status-401.md) (GET, quirk): Gates the resource with 401 Unauthorized instead of 402 Payment Required.
- [Returns 403 instead of 402](https://grug402.dev/scenario/status-403.md) (GET, quirk): Gates the resource with 403 Forbidden instead of 402.
- [Returns 400 instead of 402](https://grug402.dev/scenario/status-400.md) (GET, quirk): Gates the resource with 400 Bad Request instead of 402.
- [Returns 200 with content (no gate)](https://grug402.dev/scenario/status-200-leak.md) (GET, quirk): Serves the protected content for free with a 200 and no payment gate.
- [402 without PAYMENT-REQUIRED header](https://grug402.dev/scenario/missing-payment-required.md) (GET, quirk): Returns a 402 but omits the PAYMENT-REQUIRED header entirely.
- [PAYMENT-REQUIRED is raw JSON (not base64)](https://grug402.dev/scenario/header-raw-json.md) (GET, quirk): Sends the PAYMENT-REQUIRED requirements as raw JSON instead of base64.
- [PAYMENT-REQUIRED is invalid base64](https://grug402.dev/scenario/header-bad-base64.md) (GET, quirk): The PAYMENT-REQUIRED header value is not valid base64.
- [Base64 decodes to non-JSON](https://grug402.dev/scenario/header-base64-not-json.md) (GET, quirk): The PAYMENT-REQUIRED header is valid base64 but decodes to bytes that are not JSON.
- [Atomic amount as a JSON number](https://grug402.dev/scenario/amount-as-number.md) (GET, quirk): Emits the atomic amount as a JSON number instead of a string.
- [Missing x402Version](https://grug402.dev/scenario/missing-x402-version.md) (GET, quirk): The PAYMENT-REQUIRED body omits the x402Version field.
- [Legacy v1 headers (X-PAYMENT)](https://grug402.dev/scenario/legacy-v1-headers.md) (GET, quirk): Emits the legacy v1 X-PAYMENT header instead of the v2 PAYMENT-REQUIRED challenge.
- [Empty accepts array](https://grug402.dev/scenario/empty-accepts.md) (GET, quirk): The PAYMENT-REQUIRED challenge advertises an empty accepts array.
- [Unsupported payment scheme](https://grug402.dev/scenario/unsupported-scheme.md) (GET, quirk): Advertises a payment scheme the facilitator does not support.
- [Advertises mainnet, settles on testnet](https://grug402.dev/scenario/wrong-network-mainnet.md) (GET, quirk): Advertises a mainnet network id while the facilitator runs on testnet.
- [Wrong token contract address](https://grug402.dev/scenario/wrong-token.md) (GET, quirk): Advertises an asset contract address that is not SBC.
- [Wrong decimals (18 instead of 6)](https://grug402.dev/scenario/wrong-decimals.md) (GET, quirk): Expresses the amount with 18 decimals for a token that has 6.
- [Wrong payTo address](https://grug402.dev/scenario/wrong-payto.md) (GET, quirk): Advertises a payTo address that is not the merchant's wallet.
- [Missing resource URL](https://grug402.dev/scenario/missing-resource.md) (GET, quirk): The PAYMENT-REQUIRED challenge omits the resource object.
- [Already-expired quote (timeout 0)](https://grug402.dev/scenario/expired-quote.md) (GET, quirk): Sets maxTimeoutSeconds to 0, so the quote is dead on arrival.
- [Overlong validity window (1 year)](https://grug402.dev/scenario/overlong-validity.md) (GET, quirk): Sets maxTimeoutSeconds to a full year, widening the replay window.
- [Price changes every request](https://grug402.dev/scenario/changing-price.md) (GET, quirk): Increases the advertised amount on every request.
- [Dollar-string price ($0.0001)](https://grug402.dev/scenario/dollar-string-price.md) (GET, quirk): Advertises the amount as a human dollar string instead of atomic units.
- [Sensitive data in metadata](https://grug402.dev/scenario/sensitive-metadata.md) (GET, quirk): Leaks fake PII into the resource description before any payment.
- [Wrong asset transfer method](https://grug402.dev/scenario/wrong-transfer-method.md) (GET, quirk): Sets extra.assetTransferMethod to something other than permit2, so the facilitator can't move funds.
- [Advertises a zero price but still gates](https://grug402.dev/scenario/zero-price-gated.md) (GET, quirk): Advertises an amount of 0 yet still demands a payment header.
- [MPP challenge returns 401 instead of 402](https://grug402.dev/scenario/mpp-wrong-status.md) (GET, quirk): Issues the MPP payment challenge with HTTP 401 Unauthorized instead of the required 402 Payment Required.
- [MPP 402 without WWW-Authenticate](https://grug402.dev/scenario/mpp-missing-authenticate.md) (GET, quirk): Returns 402 but omits the WWW-Authenticate: Payment header entirely. The client receives a payment signal but no challenge to respond to.
- [WWW-Authenticate challenge is raw JSON (not Payment scheme)](https://grug402.dev/scenario/mpp-raw-json-authenticate.md) (GET, quirk): Emits WWW-Authenticate with the challenge embedded as raw JSON instead of the "Payment method=\"evm/charge\" challenge=\"<base64>\"" format.
- [WWW-Authenticate uses Bearer instead of Payment scheme](https://grug402.dev/scenario/mpp-bearer-authenticate.md) (GET, quirk): Returns WWW-Authenticate: Bearer realm="..." — the OAuth scheme — instead of the Payment scheme. MPP clients have no credential type to submit.

## Verification Gaps

- [Skips /verify, settles directly](https://grug402.dev/scenario/skip-verify.md) (GET, quirk): Goes straight to /settle without a /verify pre-check.
- [Grants on /verify, never settles](https://grug402.dev/scenario/verify-only-no-settle.md) (GET, quirk): Grants access once /verify succeeds and never calls /settle.
- [No verification at all](https://grug402.dev/scenario/no-facilitator-check.md) (GET, quirk): Grants access on the mere presence of a PAYMENT-SIGNATURE header.
- [Trusts client-supplied result](https://grug402.dev/scenario/trust-client-response.md) (GET, quirk): Believes a client-asserted payment result without checking the facilitator.
- [Fails open when the facilitator is down](https://grug402.dev/scenario/fail-open-timeout.md) (GET, quirk): Serves the resource anyway when the facilitator is unreachable.

## Replay / Idempotency / Race

- [Replay after success (no binding)](https://grug402.dev/scenario/replay-after-success.md) (GET, quirk): Once a payment settles, the same payload re-serves the resource forever with no re-settle.
- [Serves before settlement (verify/settle gap)](https://grug402.dev/scenario/serve-before-settle.md) (GET, quirk): Returns 200 immediately and settles in the background.

## Web / HTTP Layer

- [CORS hides payment headers](https://grug402.dev/scenario/cors-hides-headers.md) (GET, quirk): Does not expose the PAYMENT-REQUIRED and PAYMENT-RESPONSE headers via CORS.
- [HEAD reveals paid metadata](https://grug402.dev/scenario/head-leak.md) (GET, quirk): A HEAD request returns 200 with a leak header and no payment.

## Schemes (exact / upto / batch)

- [upto: settles actual usage](https://grug402.dev/scenario/upto-correct.md) (POST, compliant): Authorizes a maximum but settles only the actual usage (~60%).
- [upto: always charges the max](https://grug402.dev/scenario/upto-charges-max.md) (POST, quirk): Settles the full authorized max even when usage is lower.
- [upto: override exceeds the max](https://grug402.dev/scenario/upto-override-exceeds-max.md) (POST, quirk): Sets the settlement amount above the authorized maximum.
- [upto: settles zero (free)](https://grug402.dev/scenario/upto-zero-free.md) (POST, quirk): A settlement override of 0 gives the service away by accident.
- [batch-settlement: deposit, then reconcile usage](https://grug402.dev/scenario/batch-settlement-correct.md) (POST, compliant): Advertises the batch-settlement scheme: the client authorizes a 5x deposit, and the server settles only the actual (much lower) usage.
- [batch-settlement: settles beyond the deposit](https://grug402.dev/scenario/batch-settlement-overcharge-deposit.md) (POST, quirk): Settles more than the deposit the client authorized under the batch scheme.
- [batch-settlement: never reconciles down](https://grug402.dev/scenario/batch-settlement-never-reconciles.md) (POST, quirk): Holds the full authorized deposit and never settles the lower real usage.
- [exact: canonical scheme identifier (client sends the wrong one)](https://grug402.dev/scenario/client-scheme-mismatch.md) (GET, compliant): 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.

## Multi-Network Selection (MCP)

- [Genuine multi-entry accepts (match by network)](https://grug402.dev/scenario/multi-network-accepts.md) (GET, compliant): Advertises two EVM entries — Base Sepolia first, Radius testnet second — so the client must match by network instead of taking accepts[0].
- [SVM-only requirement (solana:*)](https://grug402.dev/scenario/svm-requirement.md) (GET, quirk): Advertises only a Solana (solana:*) requirement, which an EVM testnet client cannot satisfy.
- [Cross-VM accepts (EVM and SVM together)](https://grug402.dev/scenario/cross-vm-accepts.md) (GET, compliant): Advertises a Solana entry and a Radius testnet EVM entry together, forcing the client to select the VM and network it can settle on.

## Client Policy Gate (MCP)

- [Overpriced vs per-call cap](https://grug402.dev/scenario/overpriced-vs-cap.md) (GET, quirk): Advertises a price far above a sane per-call cap that an MCP policy gate would enforce.
- [Resource host mismatch](https://grug402.dev/scenario/resource-host-mismatch.md) (GET, quirk): The advertised resource origin differs from the server actually being called.
- [Tool / operation mismatch](https://grug402.dev/scenario/tool-operation-mismatch.md) (GET, quirk): The requirement authorizes a different, more privileged tool than the one being requested.
- [Session price escalation](https://grug402.dev/scenario/session-price-escalation.md) (GET, quirk): The price climbs call over call (1x, 2x, 3x...) to defeat a per-session spend limit.

## Facilitator Dependency

- [Dead facilitator URL](https://grug402.dev/scenario/dead-facilitator.md) (GET, quirk): Points /verify and /settle at a facilitator host that does not exist.
- [Facilitator can't support the network](https://grug402.dev/scenario/unsupported-network-facilitator.md) (GET, quirk): Advertises a network the facilitator does not handle.
- [Facilitator rate-limits /verify (429 → misclassified 402)](https://grug402.dev/scenario/facilitator-rate-limited.md) (GET, quirk): The facilitator returns 429 during /verify; the merchant misclassifies it as a payment failure and returns 402 instead of 503 + Retry-After.
- [Facilitator temporarily unavailable (503)](https://grug402.dev/scenario/facilitator-soft-down-503.md) (GET, quirk): The facilitator is reachable but returns 503 Service Unavailable — it is not a dead URL, just temporarily down.
- [Settle returns 500 (ambiguous paid state)](https://grug402.dev/scenario/facilitator-settle-500.md) (GET, quirk): /verify succeeds but /settle returns 500 Internal Server Error, leaving it unknown whether the payment landed on-chain.
- [Settle succeeds without txHash](https://grug402.dev/scenario/facilitator-missing-txhash.md) (GET, quirk): /settle reports success: true but omits every transaction-hash field; the PAYMENT-RESPONSE receipt carries no on-chain proof.
- [Non-JSON facilitator response leaked in 402](https://grug402.dev/scenario/facilitator-non-json-leaks.md) (GET, quirk): 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.
- [Schema drift: valid vs isValid (loose check grants)](https://grug402.dev/scenario/facilitator-schema-drift.md) (GET, quirk): 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.
- [No timeout on /verify (hangs indefinitely)](https://grug402.dev/scenario/facilitator-no-verify-timeout.md) (GET, quirk): The merchant's /verify fetch has no AbortController or timeout; when the facilitator is slow the handler ties up the connection forever.
- [Wrong payTo used at settle time](https://grug402.dev/scenario/facilitator-wrong-payto-at-settle.md) (GET, quirk): 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.
- [Grants access despite settle failure](https://grug402.dev/scenario/facilitator-already-settled-grants.md) (GET, quirk): 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.
- [Concurrent settle race (second racer gets 402)](https://grug402.dev/scenario/facilitator-settle-concurrent-race.md) (GET, quirk): 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.

## Paid-but-Denied / Unpaid-Service

- [Settles, then the handler fails](https://grug402.dev/scenario/settle-then-fail.md) (GET, quirk): The payment settles on-chain, then the handler returns 500.
- [Success without PAYMENT-RESPONSE](https://grug402.dev/scenario/missing-payment-response.md) (GET, quirk): Settles and returns 200 but omits the PAYMENT-RESPONSE header.
- [MPP paid-but-denied (403 after valid credential)](https://grug402.dev/scenario/mpp-paid-denied.md) (GET, quirk): 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.

## Edge / CDN Controls

- [Correct edge cache headers (compliant)](https://grug402.dev/scenario/edge-correct-headers.md) (GET, compliant): 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.
- [Cacheable 402 challenge](https://grug402.dev/scenario/edge-cacheable-challenge.md) (GET, quirk): Sends the 402 with Cache-Control: public, max-age, so a shared cache stores and replays a stale challenge.
- [Cacheable paid response (shared cache)](https://grug402.dev/scenario/edge-cacheable-paid.md) (GET, quirk): Sends the paid 200 publicly cacheable, so a shared edge serves paid content to unpaid clients on the same URL.
- [Missing Vary on payment](https://grug402.dev/scenario/edge-missing-vary.md) (GET, quirk): Caches the paid 200 but ignores the payment header in the cache key (no Vary), so one payer's response is served to another.
- [stale-while-revalidate leak](https://grug402.dev/scenario/edge-swr-leak.md) (GET, quirk): A stale-while-revalidate directive lets the edge serve cached protected content without payment during revalidation.
- [Oversized challenge header](https://grug402.dev/scenario/edge-oversized-header.md) (GET, quirk): Pads the PAYMENT-REQUIRED header beyond common proxy header-size caps, so an edge truncates, drops, or 431s it.
- [Conditional 304 replay](https://grug402.dev/scenario/edge-conditional-304.md) (GET, quirk): The paid resource carries an ETag, and a later conditional request is answered 304 from cache without re-payment.
- [Rate-limited before payment](https://grug402.dev/scenario/edge-rate-limited.md) (GET, quirk): Returns 429 (short Retry-After, no challenge) before the 402, so a willing payer can't tell a rate limit from a paywall.
- [WAF blocks the payment payload](https://grug402.dev/scenario/edge-waf-blocks-payment.md) (GET, quirk): The large base64 PAYMENT-SIGNATURE trips a managed WAF rule and is rejected with 403 before the payment logic runs.
- [Body too large (413)](https://grug402.dev/scenario/edge-body-too-large.md) (POST, quirk): A paid POST is rejected by a small edge body cap with 413 before reaching the payment logic.
- [Edge timeout during settle](https://grug402.dev/scenario/edge-timeout-settle.md) (GET, quirk): Settlement is slow and the edge returns 504 while the charge may have completed on-chain — an ambiguous paid state.
