grug402 — HTTP 402 reference catalog

A reference catalog of HTTP 402 (x402) payment endpoints — compliant flows alongside deliberate protocol quirks — for testing how your x402 client handles real verify and settle on the Radius testnet.

Network: eip155:72344 (chain 72344) · 84 endpoints

StatusMethodEndpointSummary
CompliantGETCanonical GET (real verify and settle)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.
CompliantPOSTCanonical POST with bodyThe same compliant flow as the canonical GET, for a POST that carries a JSON body.
CompliantGETMinimal price (1 atomic unit)A compliant endpoint priced at the smallest atomic unit, 0.000001 SBC.
CompliantGETSingle testnet accepts entryAdvertises one testnet requirement that a correct client selects by matching its network.
CompliantGETCorrect idempotency (cached retry)Caches the result by payment id, so retrying the same payment returns the cached 200 without charging twice.
CompliantGETWeather API (pay-per-call)A themed but fully compliant data endpoint, gated by a real settlement.
CompliantGETFX rate API (pay-per-call)A themed compliant endpoint that returns an exchange rate after settlement.
CompliantPOSTHaiku generator (POST, paid)A compliant POST endpoint that returns generated content after settlement.
QuirkGETReturns 401 instead of 402Gates the resource with 401 Unauthorized instead of 402 Payment Required.
QuirkGETReturns 403 instead of 402Gates the resource with 403 Forbidden instead of 402.
QuirkGETReturns 400 instead of 402Gates the resource with 400 Bad Request instead of 402.
QuirkGETReturns 200 with content (no gate)Serves the protected content for free with a 200 and no payment gate.
QuirkGET402 without PAYMENT-REQUIRED headerReturns a 402 but omits the PAYMENT-REQUIRED header entirely.
QuirkGETPAYMENT-REQUIRED is raw JSON (not base64)Sends the PAYMENT-REQUIRED requirements as raw JSON instead of base64.
QuirkGETPAYMENT-REQUIRED is invalid base64The PAYMENT-REQUIRED header value is not valid base64.
QuirkGETBase64 decodes to non-JSONThe PAYMENT-REQUIRED header is valid base64 but decodes to bytes that are not JSON.
QuirkGETAtomic amount as a JSON numberEmits the atomic amount as a JSON number instead of a string.
QuirkGETMissing x402VersionThe PAYMENT-REQUIRED body omits the x402Version field.
QuirkGETLegacy v1 headers (X-PAYMENT)Emits the legacy v1 X-PAYMENT header instead of the v2 PAYMENT-REQUIRED challenge.
QuirkGETEmpty accepts arrayThe PAYMENT-REQUIRED challenge advertises an empty accepts array.
QuirkGETUnsupported payment schemeAdvertises a payment scheme the facilitator does not support.
QuirkGETAdvertises mainnet, settles on testnetAdvertises a mainnet network id while the facilitator runs on testnet.
QuirkGETWrong token contract addressAdvertises an asset contract address that is not SBC.
QuirkGETWrong decimals (18 instead of 6)Expresses the amount with 18 decimals for a token that has 6.
QuirkGETWrong payTo addressAdvertises a payTo address that is not the merchant's wallet.
QuirkGETMissing resource URLThe PAYMENT-REQUIRED challenge omits the resource object.
QuirkGETAlready-expired quote (timeout 0)Sets maxTimeoutSeconds to 0, so the quote is dead on arrival.
QuirkGETOverlong validity window (1 year)Sets maxTimeoutSeconds to a full year, widening the replay window.
QuirkGETPrice changes every requestIncreases the advertised amount on every request.
QuirkGETDollar-string price ($0.0001)Advertises the amount as a human dollar string instead of atomic units.
QuirkGETSensitive data in metadataLeaks fake PII into the resource description before any payment.
QuirkGETSkips /verify, settles directlyGoes straight to /settle without a /verify pre-check.
QuirkGETGrants on /verify, never settlesGrants access once /verify succeeds and never calls /settle.
QuirkGETNo verification at allGrants access on the mere presence of a PAYMENT-SIGNATURE header.
QuirkGETTrusts client-supplied resultBelieves a client-asserted payment result without checking the facilitator.
QuirkGETFails open when the facilitator is downServes the resource anyway when the facilitator is unreachable.
QuirkGETReplay after success (no binding)Once a payment settles, the same payload re-serves the resource forever with no re-settle.
QuirkGETServes before settlement (verify/settle gap)Returns 200 immediately and settles in the background.
QuirkGETCORS hides payment headersDoes not expose the PAYMENT-REQUIRED and PAYMENT-RESPONSE headers via CORS.
QuirkGETHEAD reveals paid metadataA HEAD request returns 200 with a leak header and no payment.
CompliantPOSTupto: settles actual usageAuthorizes a maximum but settles only the actual usage (~60%).
QuirkPOSTupto: always charges the maxSettles the full authorized max even when usage is lower.
QuirkPOSTupto: override exceeds the maxSets the settlement amount above the authorized maximum.
QuirkPOSTupto: settles zero (free)A settlement override of 0 gives the service away by accident.
QuirkGETDead facilitator URLPoints /verify and /settle at a facilitator host that does not exist.
QuirkGETFacilitator can't support the networkAdvertises a network the facilitator does not handle.
QuirkGETFacilitator rate-limits /verify (429 → misclassified 402)The facilitator returns 429 during /verify; the merchant misclassifies it as a payment failure and returns 402 instead of 503 + Retry-After.
QuirkGETFacilitator temporarily unavailable (503)The facilitator is reachable but returns 503 Service Unavailable — it is not a dead URL, just temporarily down.
QuirkGETSettle returns 500 (ambiguous paid state)/verify succeeds but /settle returns 500 Internal Server Error, leaving it unknown whether the payment landed on-chain.
QuirkGETSettle succeeds without txHash/settle reports success: true but omits every transaction-hash field; the PAYMENT-RESPONSE receipt carries no on-chain proof.
QuirkGETNon-JSON facilitator response leaked in 402The facilitator returns an HTML maintenance page instead of JSON; the merchant forwards the raw HTML in the 402 response body, leaking internal infrastructure details.
QuirkGETSchema drift: valid vs isValid (loose check grants)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.
QuirkGETNo timeout on /verify (hangs indefinitely)The merchant's /verify fetch has no AbortController or timeout; when the facilitator is slow the handler ties up the connection forever.
QuirkGETWrong payTo used at settle timeThe 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.
QuirkGETGrants access despite settle failureWhen /settle returns a failure (e.g., already used), the merchant grants access anyway — a replay attacker can reuse any previously-rejected permit for free.
QuirkGETConcurrent settle race (second racer gets 402)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.
QuirkGETSettles, then the handler failsThe payment settles on-chain, then the handler returns 500.
QuirkGETSuccess without PAYMENT-RESPONSESettles and returns 200 but omits the PAYMENT-RESPONSE header.
CompliantGETJoke API (pay-per-call)A compliant GET that returns a joke after a real settlement.
CompliantGETDictionary API (pay-per-call)A compliant GET that returns a definition after settlement.
CompliantGETQuote API (pay-per-call)A compliant GET that returns a quote after settlement.
QuirkGETWrong asset transfer methodSets extra.assetTransferMethod to something other than permit2, so the facilitator can't move funds.
QuirkGETAdvertises a zero price but still gatesAdvertises an amount of 0 yet still demands a payment header.
CompliantPOSTbatch-settlement: deposit, then reconcile usageAdvertises the batch-settlement scheme: the client authorizes a 5x deposit, and the server settles only the actual (much lower) usage.
QuirkPOSTbatch-settlement: settles beyond the depositSettles more than the deposit the client authorized under the batch scheme.
QuirkPOSTbatch-settlement: never reconciles downHolds the full authorized deposit and never settles the lower real usage.
CompliantGETGenuine multi-entry accepts (match by network)Advertises two EVM entries — Base Sepolia first, Radius testnet second — so the client must match by network instead of taking accepts[0].
QuirkGETSVM-only requirement (solana:*)Advertises only a Solana (solana:*) requirement, which an EVM testnet client cannot satisfy.
CompliantGETCross-VM accepts (EVM and SVM together)Advertises a Solana entry and a Radius testnet EVM entry together, forcing the client to select the VM and network it can settle on.
QuirkGETOverpriced vs per-call capAdvertises a price far above a sane per-call cap that an MCP policy gate would enforce.
QuirkGETResource host mismatchThe advertised resource origin differs from the server actually being called.
QuirkGETTool / operation mismatchThe requirement authorizes a different, more privileged tool than the one being requested.
QuirkGETSession price escalationThe price climbs call over call (1x, 2x, 3x...) to defeat a per-session spend limit.
CompliantGETCorrect edge cache headers (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.
QuirkGETCacheable 402 challengeSends the 402 with Cache-Control: public, max-age, so a shared cache stores and replays a stale challenge.
QuirkGETCacheable paid response (shared cache)Sends the paid 200 publicly cacheable, so a shared edge serves paid content to unpaid clients on the same URL.
QuirkGETMissing Vary on paymentCaches the paid 200 but ignores the payment header in the cache key (no Vary), so one payer's response is served to another.
QuirkGETstale-while-revalidate leakA stale-while-revalidate directive lets the edge serve cached protected content without payment during revalidation.
QuirkGETOversized challenge headerPads the PAYMENT-REQUIRED header beyond common proxy header-size caps, so an edge truncates, drops, or 431s it.
QuirkGETConditional 304 replayThe paid resource carries an ETag, and a later conditional request is answered 304 from cache without re-payment.
QuirkGETRate-limited before paymentReturns 429 (short Retry-After, no challenge) before the 402, so a willing payer can't tell a rate limit from a paywall.
QuirkGETWAF blocks the payment payloadThe large base64 PAYMENT-SIGNATURE trips a managed WAF rule and is rejected with 403 before the payment logic runs.
QuirkPOSTBody too large (413)A paid POST is rejected by a small edge body cap with 413 before reaching the payment logic.
QuirkGETEdge timeout during settleSettlement is slow and the edge returns 504 while the charge may have completed on-chain — an ambiguous paid state.