94 published checks · self-lints at grade A

Your 402 works.Agents still can't pay you.

A 94-check catalogue against your live 402, with a specific fix for each finding — the rules the Bazaar docs never wrote down.

  1. Ship a correct 402
  2. Get indexed
  3. Get paid

10x402 finds the blockers between those steps that are visible in your response, and gives you the fix for each finding. It cannot guarantee a Bazaar listing, demand, or a payment that settles.

10x402 is new. There are no customer stories, usage claims, or testimonials here. The evidence is the published catalogue, self-lint, and storage boundary.

Start here

The catalogue is free. You pay per report served; there is no free lint tier.

free · no payment

For a person building an endpoint

Read every check and price before you pay anything:

curl -sS https://10x402.com/check

Then use /lint for a public URL, or /lint/envelope for a response you captured from local, staging, or authenticated code. The repository's npm run buyer:lint recipe below uses x402-fetch to handle the 402 retry, caps spend at $0.10 USDC, and writes the served JSON report.

git clone https://github.com/algonormative/10x402.git
cd 10x402
npm ci
export BUYER_PRIVATE_KEY=0xYOUR_FUNDED_BASE_BUYER_PRIVATE_KEY
npm run buyer:lint -- --url https://your-endpoint.example.com/api/thing \
  --max-spend-usd 0.10 --out ./10x402-report.json --yes

machine surfaces

For an agent

In MCP, call x402_checks first.

  1. Ask for the quote

    Your unpaid call answers 402 with the price and payment terms. That is the quote, not an error.

  2. Your client pays and retries

    An x402-capable client holding USDC on Base or Solana reads the terms, pays, and retries the same request. No login, no API key.

  3. Read the report

    A grade, the findings, and a specific fix for each one. You are only charged for a report that is served.

Two questions, two rails
routewhat it readshow much of the catalogueprice
GET /checkCatalogue, prices, and grade rules—free
POST /lintA live public endpointAll 94 checks$0.10 per report
POST /lint/oneA live public endpointOne named check$0.015 per report
POST /presenceA live public endpointRegistry presence, by evidence$0.06 per report
POST /monitor/verdictStored daily observations of one hostOne host, day by day$0.005 per report
POST /monitor/historyStored daily observations of one hostOne host, day by day$0.03 per report
POST /monitor/receiptStored daily observations of one hostOne host, day by day$0.12 per report
POST /lint/envelopeA captured or local 402 responseAll 94 checks$0.04 per report
POST /lint/envelope/oneA captured or local 402 responseOne named check$0.004 per report

The two scopes are two products. A full report is priced for the incident it resolves: a 402 that passes validate and still is not indexed is the class of problem that eats weeks, because nothing in the stack says which of the 94 things is wrong. At $0.10 it is a fraction of the $25 a signed conformance report costs. A single check is the CI product — run on every commit, against one property — and stays micro for that reason.

A full 94-check report costs 6.667x one check on a live URL and 10x on a pasted response — a 14.1x and 9.4x per-check advantage. Singles stay the cheaper buy through 6 questions live and 9 pasted; past that, buy the report. A pasted response costs less than a live one at both scopes, because there is no outbound request to make on your behalf. Every price is per served report: a bad URL, an unreachable target, a malformed paste or an unknown check id settles nothing, even when the payment verified.

Ship a correct 402 → get indexed → get paid

Ship a correct 402

Check the HTTP response, v1 body, v2 PAYMENT-REQUIRED header, and the fields an agent must sign.

Remove indexing blockers

Check Bazaar metadata, info-to-schema consistency, discoverability flags, and what an unpaid probe receives.

Publish payable terms

Check that an agent can read the amount, asset, network, recipient, and EIP-712 domain. The linter does not make a payment.

Why an x402 endpoint passes validate but is not indexed

Validation, discovery, and payment do not all read the same parts of a 402. If your x402 service is not showing up in Bazaar, the base envelope may be valid while discovery metadata is missing, placed incorrectly, or inconsistent with its schema.

A url-safe base64 v2 envelope can be rejected before it is decoded. Missing EIP-712 extra fields can make the client and facilitator sign different domains. A free response can give an unpaid discovery probe a 200 when it expects a 402. These are response-level blockers 10x402 can surface; it does not inspect Bazaar’s index or infer demand.

Choose a lint, then pay and retry

Every paid route answers 402 first. The v1 terms are in the JSON body and the v2 terms are standard base64 in the PAYMENT-REQUIRED header. An x402-capable client holding USDC on Base or Solana reads those terms, pays, and retries the same request — the accepts array in the 402 is the authoritative list of rails. There is no login or API key; the payment is the authorization.

POST /lint — $0.10 per report

Sends ONE unauthenticated request to the URL you name and lints the response: HTTP status, the v1 body envelope, the v2 PAYMENT-REQUIRED header envelope, dual-stack consistency, and CDP Bazaar discovery requirements. Returns a grade and a specific fix for each finding. It identifies technical blockers; it does not verify a listing or payment.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/lint \
  -H 'content-type: application/json' \
  -d '{"url":"https://toolshed.lemon-agent.dev/convert/md-html","method":"POST"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "grade": "A",
  "summary": {
    "versions_detected": [
      1,
      2
    ],
    "payTo": "0x0adc7adac7bbaffffbd73505711e42378c4f9f9e",
    "network": "eip155:8453",
    "price": "$0.001 (1000 atomic)",
    "bazaar_ready": true,
    "blockers": []
  },
  "findings": [
    {
      "severity": "info",
      "code": "BILLING_TERMS_DISCLOSED",
      "message": "no machine-readable billing-terms object was found. Consulted: the 402 (accepts[].extra.billing_terms); /.well-known/x402 resources[].billing_terms (not supplied); OpenAPI x-billing-terms (not supplied). Prose in `description` was not parsed.",
      "fix": "Publish a billing-terms object with all four keys, in any ONE of: `accepts[].extra.billing_terms` in the 402 (always read), `resources[].billing_terms` in /.well-known/x402, or `x-billing-terms` on the OpenAPI operation (both read only when the caller supplies them). e.g. {\"billable_unit\":\"1 second\",\"hold\":\"the quote, released in 24h\",\"acceptance_observer\":\"the buyer\",\"silence_window\":\"72h\"}. Any JSON value counts; prose in `description` is not parsed. algonormative/10x402#10's block gives billable_unit and hold only: add acceptance_observer and silence_window. NOTHING REQUIRES THIS (no spec, client or registry): informational, moves neither verdict.",
      "core": false,
      "sources": [
        {
          "kind": "field-report",
          "ref": "Moltbook 92cd240a… (bitroadai), \"transport success is not billable completion\" — buyers asked for the minimum billable unit and what a hold covers"
        },
        {
          "kind": "field-report",
          "ref": "Moltbook 6868a451… / 2d2df453… (relayzero, @miacollective) — who observes acceptance, and how long silence counts as acceptance, as terms a buyer needs before paying"
        },
        {
          "kind": "house-opinion",
          "ref": "no spec requires these four, no client parses them, no registry demands them — this check can only REPORT. Key names from house PR algonormative/10x402#10"
        },
        {
          "kind": "spec",
          "ref": "specs/x402-specification-v2.md § 6.1 (Payment Flow Models), read 2026-09-09 — SILENT on all four",
          "context": true
        }
      ]
    }
  ],
  "checks_run": 78
}

POST /lint/one — $0.015 per report

The same outbound probe as /lint, reported for exactly one check you name. For settling a single question — "is my v2 header base64url", "does my bazaar info validate against its own schema" — without buying the whole catalogue. The answer says whether the check PASSED, and when the check did not apply to this response it says that instead of quietly passing. GET /check lists every check id, and publishes the batch arithmetic — so you can work out where the full report becomes the cheaper buy before paying for any of it.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/lint/one \
  -H 'content-type: application/json' \
  -d '{"url":"https://toolshed.lemon-agent.dev/convert/md-html","method":"POST","check":"V2_B64_URLSAFE"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "check": "V2_B64_URLSAFE",
  "applied": true,
  "passed": true,
  "finding": null,
  "regime": "payment",
  "severity": "error",
  "core": true,
  "sources": [
    {
      "kind": "client-code",
      "ref": "@x402/[email protected] dist/cjs/utils/index.js:133 — Base64EncodedRegex = /^[A-Za-z0-9+/]*={0,2}$/"
    },
    {
      "kind": "client-code",
      "ref": "@x402/[email protected] dist/cjs/http/index.js:1778-1781 — the regex is tested on the RAW header, then it throws, before any decode"
    },
    {
      "kind": "spec",
      "ref": "specs/transports-v2/http.md:7-25 § Payment Required Signaling — \"Base64-encoded\", SILENT on the alphabet",
      "context": true
    }
  ],
  "summary": {
    "versions_detected": [
      1,
      2
    ],
    "payTo": "0x0adc7adac7bbaffffbd73505711e42378c4f9f9e",
    "network": "eip155:8453",
    "price": "$0.001 (1000 atomic)"
  },
  "checks_run": 1
}

POST /presence — $0.06 per report

The question the stuck-seller threads open with: "I settle payments — why can nobody find me?" Fetches your 402, reads the payTo and resource it declares, then checks three public surfaces: the full CDP Bazaar discovery catalog (scanned end to end — its payTo filter is documented but inert, so the honest read is the whole catalog), the x402scan explorer, and USDC transfer activity to your payTo on Base. Per-registry verdict with the evidence and a specific way in for each miss. A surface that cannot be read reports `unknown`, never a guessed `not_found`. /lint answers whether your declaration is right; this answers whether the world can see it.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/presence \
  -H 'content-type: application/json' \
  -d '{"url":"https://toolshed.lemon-agent.dev/convert/md-html","method":"POST"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "target": {
    "url": "https://10x402.com/lint/one",
    "method": "POST",
    "status": 402
  },
  "identity": {
    "payTo": [
      "0x885E7BEF433eb78F5976b28A7c10739c98DB11E5"
    ],
    "declaredUrl": "https://10x402.com/lint/one"
  },
  "registries": {
    "bazaar": {
      "verdict": "listed",
      "evidence": {
        "source": "CDP discovery catalog, scanned in full",
        "catalog_total": 15060,
        "matches": [
          {
            "resource": "https://10x402.com/lint/one",
            "x402Version": 2,
            "lastUpdated": "2026-08-20T04:20:41.394Z",
            "payTo": "0x885E7BEF433eb78F5976b28A7c10739c98DB11E5"
          }
        ],
        "resources_on_same_payTo": 4
      },
      "fix": null
    },
    "x402scan": {
      "verdict": "listed",
      "evidence": {
        "source": "x402scan public explorer API, looked up by payTo",
        "match": {
          "resource": "https://10x402.com/lint/one",
          "method": "POST",
          "x402Version": 2,
          "lastUpdated": "2026-08-20T04:21:14.065Z"
        }
      },
      "fix": null
    }
  },
  "onchain": {
    "verdict": "active",
    "evidence": {
      "source": "Base via Blockscout tokentx",
      "incoming_transfers_in_window": 4,
      "window": "all transfers",
      "latest": {
        "value": "100000",
        "tokenSymbol": "USDC",
        "timeStamp": "1787199643",
        "hash": "0x3321c354d365c5ddd1494edda84bb2ccd0191303638f9dfb280b8c7303c7b675",
        "from": "0x632ff2f904cc6ab6d741a42014c4c483f328e92f",
        "to": "0x885e7bef433eb78f5976b28a7c10739c98db11e5",
        "contractAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
      }
    },
    "fix": null
  },
  "summary": {
    "listed": 2,
    "of": 2,
    "unknown": 0,
    "settlement_seen": true
  },
  "notes": [
    "Verdicts are observations of public read surfaces at one moment, not guarantees. An `unknown` means the surface could not be read and says nothing about the listing.",
    "x402-list (manual registry) is not machine-checkable and is not covered here."
  ]
}

POST /monitor/verdict — $0.005 per report

The latest stored day for one host: agenteconomy.report, apistrust.com and the CDP Bazaar quality block side by side, plus what the endpoint itself answered to an unpaid request on the verb its own catalogue row declares and on GET. Read-time flags name the two findings that matter — a liveness contradiction between the instruments, and `wrongly-dead`: rated at uptime 0.0 while answering 402 on its declared verb. Stamped with the day it was measured, and if the stored probe is more than 36 hours old the answer says so instead of pretending to be current. Nothing is fetched to serve it.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/monitor/verdict \
  -H 'content-type: application/json' \
  -d '{"host":"10x402.com"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "kind": "monitor",
  "endpoint": "verdict",
  "host": "10x402.com",
  "subject_kind": "host",
  "probeable": true,
  "as_of": "2026-08-27",
  "state": "probed",
  "freshness": {
    "probed_at": "2026-08-27T11:47:12.000Z",
    "probe_age_hours": 0.2,
    "stale": false,
    "stale_after_hours": 36,
    "day": "2026-08-27",
    "days_behind_utc_today": 0,
    "reads": "the stored probe is 0.2 h old, inside the 36 h bound."
  },
  "instruments": {
    "agenteconomy": {
      "source": "agenteconomy.report/s/ratings.json (CC BY 4.0)",
      "observed": true,
      "uptime": 0,
      "score": 0,
      "tier": "D",
      "settled_usd_14d": 1.33,
      "organic_paying_agents": 5,
      "flag": "NEW",
      "reads": "uptime 0.0 — dead at this instrument, score 0, tier D, $1.33 settled over 14 days, 5 organic paying agents (the rater's own term for its non-bot payer count), flagged NEW"
    },
    "apistrust": {
      "source": "apistrust.com host table",
      "observed": true,
      "score": 100,
      "endpoints": 4,
      "endpoints_down": 0,
      "reads": "score 100, 0 of 4 endpoints down"
    },
    "bazaar": {
      "source": "CDP Bazaar discovery catalogue, per-resource quality block",
      "observed": true,
      "calls_30d": 4,
      "unique_payers_30d": 3,
      "last_called_at": "2026-08-27T03:22:07.269Z",
      "resource": "https://10x402.com/lint/one",
      "declared_method": "POST",
      "reads": "4 calls in 30 days, 3 unique payers, last called 2026-08-27T03:22:07.269Z, declares POST"
    }
  },
  "probe": {
    "ran": true,
    "ts": 1787831232,
    "at": "2026-08-27T11:47:12.000Z",
    "declared_method": "POST",
    "declared": {
      "asked": true,
      "answered": true,
      "status": 402,
      "latency_ms": null,
      "reads": "answered 402 (latency not recorded)"
    },
    "get": {
      "asked": true,
      "answered": true,
      "status": 405,
      "latency_ms": null,
      "reads": "answered 405 (latency not recorded)"
    },
    "evidence": {
      "payment_required_header": {
        "seen": true,
        "reads": "the x402 v2 PAYMENT-REQUIRED header was present"
      },
      "v1_body_x402version": {
        "seen": true,
        "reads": "an x402 v1 body carrying x402Version was present"
      },
      "note": "Both are read from the DECLARED-verb response — a GET-only reading of a POST endpoint is the wrong reading."
    },
    "reads": "on POST, its declared verb, it answered 402 (latency not recorded); on GET it answered 405 (latency not recorded) — which is exactly the shape a GET-only rater records as dead"
  },
  "flags": [
    {
      "id": "liveness-contradiction",
      "statement": "agenteconomy.report rated this host dead — uptime 0.0 — while apistrust.com recorded 0 of its endpoints down: none. Both instruments had data for this host; one says dead, the other says live."
    },
    {
      "id": "wrongly-dead",
      "statement": "Rated at uptime 0.0 — but when this service sent an unpaid POST to the resource its own catalogue row declares, it answered 402: the response a live, paid x402 endpoint gives. The rating and the endpoint disagree, and the endpoint was asked directly."
    }
  ],
  "notes": [
    "Every number here is a STORED observation of a public surface, re-served. Nothing was fetched to answer this call, so the `as_of` day is the truth about when it was measured.",
    "NULL and 0 are different claims throughout: a NULL instrument column means that instrument had no row for this host that day, a 0 means it reported zero, and a probe status of 0 means it was asked and answered nothing at all.",
    "Flags are computed at read time from the stored rows, never stored. GET /monitor names the roster criteria in full, free."
  ]
}

POST /monitor/history — $0.03 per report

The full daily series for one host, oldest first: what each instrument reported that day and what the declared-verb probe found, with the flags computed per day. This is the half the free page deliberately does not give away — a single day says what a rating is, and the series says whether it is drifting, whether a correction stuck, and how long a wrong liveness reading has been costing you. A day the capture did not run is simply absent, and a day that was captured but not probed says so rather than reading as a silent endpoint.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/monitor/history \
  -H 'content-type: application/json' \
  -d '{"host":"10x402.com"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "kind": "monitor",
  "endpoint": "history",
  "host": "10x402.com",
  "subject_kind": "host",
  "probeable": true,
  "as_of": "2026-08-27",
  "state": "series",
  "days_held": 1,
  "first_day": "2026-08-27",
  "last_day": "2026-08-27",
  "probed_days": 1,
  "series": [
    {
      "day": "2026-08-27",
      "state": "probed",
      "instruments": {
        "agenteconomy": {
          "source": "agenteconomy.report/s/ratings.json (CC BY 4.0)",
          "observed": true,
          "uptime": 0,
          "score": 0,
          "tier": "D",
          "settled_usd_14d": 1.33,
          "organic_paying_agents": 5,
          "flag": "NEW",
          "reads": "uptime 0.0 — dead at this instrument, score 0, tier D, $1.33 settled over 14 days, 5 organic paying agents (the rater's own term for its non-bot payer count), flagged NEW"
        },
        "apistrust": {
          "source": "apistrust.com host table",
          "observed": true,
          "score": 100,
          "endpoints": 4,
          "endpoints_down": 0,
          "reads": "score 100, 0 of 4 endpoints down"
        },
        "bazaar": {
          "source": "CDP Bazaar discovery catalogue, per-resource quality block",
          "observed": true,
          "calls_30d": 4,
          "unique_payers_30d": 3,
          "last_called_at": "2026-08-27T03:22:07.269Z",
          "resource": "https://10x402.com/lint/one",
          "declared_method": "POST",
          "reads": "4 calls in 30 days, 3 unique payers, last called 2026-08-27T03:22:07.269Z, declares POST"
        }
      },
      "probe": {
        "ran": true,
        "ts": 1787831232,
        "at": "2026-08-27T11:47:12.000Z",
        "declared_method": "POST",
        "declared": {
          "asked": true,
          "answered": true,
          "status": 402,
          "latency_ms": null,
          "reads": "answered 402 (latency not recorded)"
        },
        "get": {
          "asked": true,
          "answered": true,
          "status": 405,
          "latency_ms": null,
          "reads": "answered 405 (latency not recorded)"
        },
        "evidence": {
          "payment_required_header": {
            "seen": true,
            "reads": "the x402 v2 PAYMENT-REQUIRED header was present"
          },
          "v1_body_x402version": {
            "seen": true,
            "reads": "an x402 v1 body carrying x402Version was present"
          },
          "note": "Both are read from the DECLARED-verb response — a GET-only reading of a POST endpoint is the wrong reading."
        },
        "reads": "on POST, its declared verb, it answered 402 (latency not recorded); on GET it answered 405 (latency not recorded) — which is exactly the shape a GET-only rater records as dead"
      },
      "flags": [
        {
          "id": "liveness-contradiction",
          "statement": "agenteconomy.report rated this host dead — uptime 0.0 — while apistrust.com recorded 0 of its endpoints down: none. Both instruments had data for this host; one says dead, the other says live."
        },
        {
          "id": "wrongly-dead",
          "statement": "Rated at uptime 0.0 — but when this service sent an unpaid POST to the resource its own catalogue row declares, it answered 402: the response a live, paid x402 endpoint gives. The rating and the endpoint disagree, and the endpoint was asked directly."
        }
      ]
    }
  ],
  "notes": [
    "Ordered oldest first, one entry per UTC day this wing captured. A day missing from this series is a day the capture did not run or did not see this host — it is not a day the host was absent from the market.",
    "An entry with `state: \"readings-only\"` was captured but not probed: the roster is capped, and being off it says nothing about the host."
  ]
}

POST /monitor/receipt — $0.12 per report

The artefact to attach to a corrections request. It carries the whole daily series, a CONTRADICTION STATEMENT in plain numbers — how many days two instruments disagreed about whether this host was alive, and on how many of them the endpoint answered 402 to an unpaid request on its declared verb — a SHA-256 digest over the canonical JSON of the document so two copies can be compared in one line, and an attestation naming the probe method, the exact User-Agent every request carried (searchable verbatim in the rater's own access log), and the fact that NO PAYMENT WAS EVER SENT. The digest is an integrity check, not a signature: it proves two copies are the same document, not who issued it.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/monitor/receipt \
  -H 'content-type: application/json' \
  -d '{"host":"10x402.com"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "kind": "monitor",
  "endpoint": "receipt",
  "host": "10x402.com",
  "subject_kind": "host",
  "probeable": true,
  "issued_at": "2026-08-27T12:00:00.000Z",
  "as_of": "2026-08-27",
  "state": "receipt",
  "days_held": 1,
  "first_day": "2026-08-27",
  "last_day": "2026-08-27",
  "probed_days": 1,
  "contradiction": {
    "days_held": 1,
    "days_in_contradiction": 1,
    "days_wrongly_dead": 1,
    "days_wrongly_dead_candidate": 0,
    "statement": "On 1 of the 1 day(s) held, the two probing instruments disagreed about whether 10x402.com was alive. Most recently, on 2026-08-27: agenteconomy.report recorded uptime 0.0, while apistrust.com recorded 0 of 4 endpoints down at score 100. On 1 of those day(s) this service sent an unpaid POST to https://10x402.com/lint/one — the resource its own catalogue row declares — and it answered 402, the response a live, paid x402 endpoint gives, while the same day's rating recorded it dead. On 2026-08-27 the same resource answered 405 to a GET, which is the reading a GET-only prober takes and files as downtime."
  },
  "series": [
    {
      "day": "2026-08-27",
      "state": "probed",
      "instruments": {
        "agenteconomy": {
          "source": "agenteconomy.report/s/ratings.json (CC BY 4.0)",
          "observed": true,
          "uptime": 0,
          "score": 0,
          "tier": "D",
          "settled_usd_14d": 1.33,
          "organic_paying_agents": 5,
          "flag": "NEW",
          "reads": "uptime 0.0 — dead at this instrument, score 0, tier D, $1.33 settled over 14 days, 5 organic paying agents (the rater's own term for its non-bot payer count), flagged NEW"
        },
        "apistrust": {
          "source": "apistrust.com host table",
          "observed": true,
          "score": 100,
          "endpoints": 4,
          "endpoints_down": 0,
          "reads": "score 100, 0 of 4 endpoints down"
        },
        "bazaar": {
          "source": "CDP Bazaar discovery catalogue, per-resource quality block",
          "observed": true,
          "calls_30d": 4,
          "unique_payers_30d": 3,
          "last_called_at": "2026-08-27T03:22:07.269Z",
          "resource": "https://10x402.com/lint/one",
          "declared_method": "POST",
          "reads": "4 calls in 30 days, 3 unique payers, last called 2026-08-27T03:22:07.269Z, declares POST"
        }
      },
      "probe": {
        "ran": true,
        "ts": 1787831232,
        "at": "2026-08-27T11:47:12.000Z",
        "declared_method": "POST",
        "declared": {
          "asked": true,
          "answered": true,
          "status": 402,
          "latency_ms": null,
          "reads": "answered 402 (latency not recorded)"
        },
        "get": {
          "asked": true,
          "answered": true,
          "status": 405,
          "latency_ms": null,
          "reads": "answered 405 (latency not recorded)"
        },
        "evidence": {
          "payment_required_header": {
            "seen": true,
            "reads": "the x402 v2 PAYMENT-REQUIRED header was present"
          },
          "v1_body_x402version": {
            "seen": true,
            "reads": "an x402 v1 body carrying x402Version was present"
          },
          "note": "Both are read from the DECLARED-verb response — a GET-only reading of a POST endpoint is the wrong reading."
        },
        "reads": "on POST, its declared verb, it answered 402 (latency not recorded); on GET it answered 405 (latency not recorded) — which is exactly the shape a GET-only rater records as dead"
      },
      "flags": [
        {
          "id": "liveness-contradiction",
          "statement": "agenteconomy.report rated this host dead — uptime 0.0 — while apistrust.com recorded 0 of its endpoints down: none. Both instruments had data for this host; one says dead, the other says live."
        },
        {
          "id": "wrongly-dead",
          "statement": "Rated at uptime 0.0 — but when this service sent an unpaid POST to the resource its own catalogue row declares, it answered 402: the response a live, paid x402 endpoint gives. The rating and the endpoint disagree, and the endpoint was asked directly."
        }
      ]
    }
  ],
  "attestation": {
    "method": "One unauthenticated HTTP request to the host's most-recently-called CDP Bazaar resource on the verb that catalogue row declares, and one on GET, taken seconds apart from a Cloudflare Worker. Redirects are not followed. At most 4 KB of each body is read.",
    "user_agent": "10x402-monitor/0.1 (+https://10x402.com/monitor)",
    "no_payment": "NO PAYMENT WAS EVER SENT. No X-PAYMENT header, no PAYMENT-SIGNATURE, no cookie and no authorization accompanied any request in this document. Every status recorded here is what an unpaid caller sees — which is the only reading comparable with what the rating instruments publish, and the reason this service is a monitor rather than a customer.",
    "schedule": {
      "capture": "17 */6 * * *",
      "probe": "47 */6 * * *",
      "timezone": "UTC"
    },
    "statement": "Every probe in this document was made by 10x402 from a Cloudflare Worker, identifying itself in every request as `10x402-monitor/0.1 (+https://10x402.com/monitor)` — searchable verbatim in the access log of the host it was made against. It sent one unauthenticated request on the verb the subject's own CDP Bazaar row declares and one on GET, followed no redirects, and read at most 4 KB of each response. NO PAYMENT WAS SENT ON ANY OF THEM: a prober that pays is buying the answer it publishes. Instruments are captured every six hours, at :17 past the hour UTC, and probes taken every six hours, at :47 past the hour UTC; the readings are re-served from storage, never re-fetched to answer a call."
  },
  "verify": "Recompute: take this document, remove the `digest` member, serialise the remainder as canonical JSON (object keys sorted by code unit at every depth, array order preserved, no whitespace, UTF-8), and SHA-256 it. The hex digest must equal `digest.value`. This is an integrity check on the document, NOT a signature: it proves two copies are the same document, and it does not prove who issued it.",
  "notes": [
    "Ordered oldest first, one entry per UTC day this wing captured. A day missing from this series is a day the capture did not run or did not see this host — it is not a day the host was absent from the market.",
    "An entry with `state: \"readings-only\"` was captured but not probed: the roster is capped, and being off it says nothing about the host."
  ],
  "digest": {
    "algorithm": "SHA-256",
    "encoding": "hex",
    "over": "every member of this document except `digest` itself",
    "canonicalisation": "object keys sorted at every depth, array order preserved, no whitespace, UTF-8",
    "value": "4804689b42260b894dc4a1323944c8986f3b22c47ea34770a83166db2cdc596e"
  }
}

POST /lint/envelope — $0.04 per report

Runs the same check catalogue against a response you already have: paste the status, headers and body. Nothing is fetched, so it works for v1/v2 migration work, on staging, on localhost and on an endpoint that is not deployed yet. Cheaper than /lint for that reason: there is no outbound request to make on your behalf.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/lint/envelope \
  -H 'content-type: application/json' \
  -d '{"status":402,"headers":{"content-type":"application/json"},"body":"{\"x402Version\":1,\"accepts\":[{\"scheme\":\"exact\",\"network\":\"base\",\"maxAmountRequired\":\"1000\",\"resource\":\"https://example.com/api/thing\",\"description\":\"an example paid endpoint\",\"mimeType\":\"application/json\",\"payTo\":\"0x0000000000000000000000000000000000000001\",\"maxTimeoutSeconds\":60,\"asset\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"extra\":{\"name\":\"USD Coin\",\"version\":\"2\"},\"outputSchema\":{\"input\":{\"type\":\"http\",\"method\":\"POST\",\"discoverable\":true,\"bodyType\":\"text\",\"description\":\"the request body\"},\"output\":{\"type\":\"string\",\"description\":\"the response body\"}}}]}"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "grade": "A",
  "summary": {
    "versions_detected": [
      1
    ],
    "payTo": "0x0000000000000000000000000000000000000001",
    "network": "base",
    "price": "$0.001 (1000 atomic)",
    "bazaar_ready": "n/a",
    "blockers": [
      "V2_HEADER_PRESENT"
    ]
  },
  "findings": [
    {
      "severity": "error",
      "code": "V2_HEADER_PRESENT",
      "message": "no PAYMENT-REQUIRED response header — this endpoint publishes no x402 v2 envelope.",
      "fix": "Add a PAYMENT-REQUIRED response header to the 402 carrying the standard-base64 JSON v2 envelope. This costs you DISCOVERY rather than payment, and the distinction is worth being precise about: @x402/core reads the header first but DOES fall back to a v1 body when there is none, so the current client generation can still pay you. What it cannot do is find you — CDP marks the PAYMENT-REQUIRED header a required indexing check, so a v1-only 402 is not catalogued at all, and a strictly-v2 client cannot pay it either. Keep the v1 body exactly as it is; the two versions share a 402 without either noticing the other.",
      "core": false,
      "sources": [
        {
          "kind": "spec",
          "ref": "specs/transports-v2/http.md:7-25 § Payment Required Signaling"
        },
        {
          "kind": "cdp-validator",
          "ref": "cdp-validator-toolshed.json preflight[6] payment_required_header (required)"
        },
        {
          "kind": "client-code",
          "ref": "@x402/[email protected] dist/cjs/http/index.js:1620-1628 — the v2 client DOES fall back to a v1 body"
        },
        {
          "kind": "field-report",
          "ref": "x402-foundation/x402#3091 — [email protected] is still a live buyer population"
        }
      ]
    },
    {
      "severity": "info",
      "code": "BILLING_TERMS_DISCLOSED",
      "message": "no machine-readable billing-terms object was found. Consulted: the 402 (accepts[].extra.billing_terms); /.well-known/x402 resources[].billing_terms (not supplied); OpenAPI x-billing-terms (not supplied). Prose in `description` was not parsed.",
      "fix": "Publish a billing-terms object with all four keys, in any ONE of: `accepts[].extra.billing_terms` in the 402 (always read), `resources[].billing_terms` in /.well-known/x402, or `x-billing-terms` on the OpenAPI operation (both read only when the caller supplies them). e.g. {\"billable_unit\":\"1 second\",\"hold\":\"the quote, released in 24h\",\"acceptance_observer\":\"the buyer\",\"silence_window\":\"72h\"}. Any JSON value counts; prose in `description` is not parsed. algonormative/10x402#10's block gives billable_unit and hold only: add acceptance_observer and silence_window. NOTHING REQUIRES THIS (no spec, client or registry): informational, moves neither verdict.",
      "core": false,
      "sources": [
        {
          "kind": "field-report",
          "ref": "Moltbook 92cd240a… (bitroadai), \"transport success is not billable completion\" — buyers asked for the minimum billable unit and what a hold covers"
        },
        {
          "kind": "field-report",
          "ref": "Moltbook 6868a451… / 2d2df453… (relayzero, @miacollective) — who observes acceptance, and how long silence counts as acceptance, as terms a buyer needs before paying"
        },
        {
          "kind": "house-opinion",
          "ref": "no spec requires these four, no client parses them, no registry demands them — this check can only REPORT. Key names from house PR algonormative/10x402#10"
        },
        {
          "kind": "spec",
          "ref": "specs/x402-specification-v2.md § 6.1 (Payment Flow Models), read 2026-09-09 — SILENT on all four",
          "context": true
        }
      ]
    }
  ],
  "checks_run": 29
}

POST /lint/envelope/one — $0.004 per report

One named check over a response you already have — the cheapest answer this service sells, and the one to reach for in a test or a CI step that asserts a single property of a 402 it just built. Nothing is fetched. The answer says whether the check PASSED, and when the check did not apply to this response it says that instead of quietly passing. GET /check lists every check id.

First, request the quote. This unpaid call returns HTTP 402 with the price and payment terms. It does not return the report yet.

curl -sS -X POST https://10x402.com/lint/envelope/one \
  -H 'content-type: application/json' \
  -d '{"status":402,"headers":{"content-type":"application/json"},"body":"{\"x402Version\":1,\"accepts\":[{\"scheme\":\"exact\",\"network\":\"base\",\"maxAmountRequired\":\"1000\",\"resource\":\"https://example.com/api/thing\",\"description\":\"an example paid endpoint\",\"mimeType\":\"application/json\",\"payTo\":\"0x0000000000000000000000000000000000000001\",\"maxTimeoutSeconds\":60,\"asset\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"extra\":{\"name\":\"USD Coin\",\"version\":\"2\"},\"outputSchema\":{\"input\":{\"type\":\"http\",\"method\":\"POST\",\"discoverable\":true,\"bodyType\":\"text\",\"description\":\"the request body\"},\"output\":{\"type\":\"string\",\"description\":\"the response body\"}}}]}","check":"V2_HEADER_PRESENT"}'

Then let an x402-capable client pay and retry the same request. A successful paid retry returns the report.

Example paid reportgenerated by the current engine

This build-computed example shows the successful paid response shape. The unpaid curl above returns the 402 quote instead.

{
  "check": "V2_HEADER_PRESENT",
  "applied": true,
  "passed": false,
  "finding": {
    "severity": "error",
    "code": "V2_HEADER_PRESENT",
    "message": "no PAYMENT-REQUIRED response header — this endpoint publishes no x402 v2 envelope.",
    "fix": "Add a PAYMENT-REQUIRED response header to the 402 carrying the standard-base64 JSON v2 envelope. This costs you DISCOVERY rather than payment, and the distinction is worth being precise about: @x402/core reads the header first but DOES fall back to a v1 body when there is none, so the current client generation can still pay you. What it cannot do is find you — CDP marks the PAYMENT-REQUIRED header a required indexing check, so a v1-only 402 is not catalogued at all, and a strictly-v2 client cannot pay it either. Keep the v1 body exactly as it is; the two versions share a 402 without either noticing the other.",
    "core": false,
    "sources": [
      {
        "kind": "spec",
        "ref": "specs/transports-v2/http.md:7-25 § Payment Required Signaling"
      },
      {
        "kind": "cdp-validator",
        "ref": "cdp-validator-toolshed.json preflight[6] payment_required_header (required)"
      },
      {
        "kind": "client-code",
        "ref": "@x402/[email protected] dist/cjs/http/index.js:1620-1628 — the v2 client DOES fall back to a v1 body"
      },
      {
        "kind": "field-report",
        "ref": "x402-foundation/x402#3091 — [email protected] is still a live buyer population"
      }
    ]
  },
  "regime": "bazaar",
  "severity": "error",
  "core": false,
  "sources": [
    {
      "kind": "spec",
      "ref": "specs/transports-v2/http.md:7-25 § Payment Required Signaling"
    },
    {
      "kind": "cdp-validator",
      "ref": "cdp-validator-toolshed.json preflight[6] payment_required_header (required)"
    },
    {
      "kind": "client-code",
      "ref": "@x402/[email protected] dist/cjs/http/index.js:1620-1628 — the v2 client DOES fall back to a v1 body"
    },
    {
      "kind": "field-report",
      "ref": "x402-foundation/x402#3091 — [email protected] is still a live buyer population"
    }
  ],
  "summary": {
    "versions_detected": [
      1
    ],
    "payTo": "0x0000000000000000000000000000000000000001",
    "network": "base",
    "price": "$0.001 (1000 atomic)"
  },
  "checks_run": 1
}

Payment terms: USDC on Base at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; base in v1 and eip155:8453 in v2. USDC on Solana may also be offered, at the same price — the accepts array in each live 402 is the authority on which rails this deployment takes, and Base is always its first entry.

You are only charged for a report that is served. A bad URL, unreachable target, or malformed paste settles nothing, even if the payment verified.

Read the report: fix payment blockers first

The grade ladder
gradewhen
Azero payment-regime errors and zero payment-regime warnings
Bzero payment-regime errors, one or two warnings
Czero payment-regime errors, three or more warnings
Done or more payment-regime errors, none of them core
Fany core error — the envelope is not usable as published

Core checks are the failures that make an envelope unusable as published. One core error is an F. Ordinary errors are a D; warnings count toward B or C.

Severities: error — a client, a facilitator or the discovery index will reject or mis-read this; warn — it works, but it costs you something you probably want; info — a nit; never affects the grade.

Two verdicts, because there are two questions

The grade answers “can I be paid”, and only that. It counts payment-regime findings: the specs’ MUSTs, and what a shipping client parses, throws on, or refuses to sign.

summary.bazaar_ready answers “can I be found” — true, false, or "n/a" for a v1-only endpoint — from bazaar-regime errors, with the blocking codes listed in summary.blockers.

They come apart more often than you would expect, and the case that matters is grade A with bazaar_ready: false: an endpoint taking payments correctly that CDP will not catalogue. That is the exact shape sellers describe as “my endpoint passes validate but is not indexed”, and reporting it as a D told them their working endpoint was broken while burying the thing that was actually wrong.

checks_run is the number of catalogue checks that applied, not the total available. A v1-only response legitimately skips v2 checks.

Two things you should not have to take on faith

It has to pass its own lint

The test suite runs the production-configured Worker under workerd, takes the 402 it actually serves for every paid endpoint, and requires grade A with zero findings. Every build also constructs both paid envelopes, self-lints them, and fails on any finding before writing dist/.

What you lint is your business

The application store keeps no linted URLs, no pasted envelopes, and no reports. It retains aggregate lint results plus the quota and payment records needed to operate the service; it does not persist the material being linted.

The suite also keeps a frozen 402 captured from a live production seller as a positive control. It is not presented as a current live-domain check.

The x402 conformance checklist: 94 published checks

91 checks inspect HTTP and x402 conformance. 2 report safeguards disclose truncated input or findings, so a partial report cannot read as clean. 1 report a disclosure buyers asked for that no specification requires, and never grade. Every finding includes a code, message, severity, and specific fix.

Each check also names its regime and its sources. 49 checks are payment — the specs’ MUSTs and what shipping clients parse or throw on, and the only findings that set the grade. 25 are bazaar — what CDP requires to index you, reported as bazaar_ready instead. 19 are hygiene, which are info and never grade. 1 is informational — a disclosure buyers have asked for in the field that no specification requires and no client or registry enforces, so it is reported and decides nothing. A rule with no citation is an assertion, so every row carries one — including the ones whose honest answer is house-opinion.

HTTP layer11 checks
HTTP layer: 11 checks
codeseverityregimewhat it checks, and where the rule comes from
HTTP_STATUS_402 errorcore payment an unauthenticated request answers 402 — not core on a 404/405, which is as often the wrong verb as a missing route, and a 200 or a redirect is delegated to HTTP_FREE_TIER_200 and HTTP_REDIRECT rather than counted here twice
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
  • specspecs/transports-v1/http.md § Payment Required Signaling
  • client-code[email protected] dist/esm/index.mjs:19 (`if (response.status !== 402) return response`)
  • cdp-validatorcdp-validator-toolshed.json preflight[3] returns_402 (required)
HTTP_FREE_TIER_200 warn payment no free tier serving 200s to unauthenticated callers
  • client-code[email protected] dist/esm/index.mjs:19 — a non-402 is returned unpaid; the client never attempts payment
  • cdp-validatorcdp-validator-toolshed.json preflight[3] returns_402 (required)
  • cdp-docshttps://docs.cdp.coinbase.com/x402/seller/get-discovered — endpoints are health-probed on an interval
  • liveCDP facilitator /verify answers some invalid payments HTTP 400 WITH a well-formed { isValid: false, invalidReason: "preflight_validation_failed" } body — a verdict on a 4xx, which no CDP doc states
  • field-reporthouse settlement record 2026-09-01--solana-rail-first-settlement (lemon-toolshed, first Solana settlement) — a smoke payment was served free because the chassis classified that HTTP 400 as "facilitator unavailable" and failed open; 10x402 shipped the same bug (worker/x402.js facilitatorCall)
HTTP_SERVER_ERROR errorcore payment the endpoint is not 5xx
  • specspecs/transports-v2/http.md:176-186 § Error Handling
  • client-code[email protected] dist/esm/index.mjs:19 — a 5xx is returned unpaid
  • cdp-validatorcdp-validator-toolshed.json preflight[2] endpoint_reachable (required)
HTTP_REDIRECT warn payment the 402 is not behind a redirect
  • client-code@x402/[email protected] dist/esm/index.mjs:10 — `await fetch(request)`, i.e. the default redirect mode, so redirects ARE followed
  • specRFC 9110 § 15.4.3 — 301/302 rewrite POST to GET; 307/308 do not
  • cdp-validatorcdp-validator-toolshed.json preflight[0] url_valid — the ADVERTISED url is what is probed
HTTP_CONTENT_TYPE_JSON warn payment the v1 envelope body is served as JSON
  • specspecs/transports-v1/http.md § Payment Required Signaling (Content-Type: application/json)
  • client-code@x402/[email protected] dist/esm/chunk-BA2VL4DT.mjs:2163 — processResponse parses the body only when content-type includes application/json
  • house-opinion[email protected] dist/esm/index.mjs:22 does NOT branch on content-type, so this costs some client paths and not the main v1 one — hence warn
ENVELOPE_PRESENT errorcore payment at least one x402 envelope is published
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
  • specspecs/transports-v1/http.md § Payment Required Signaling
  • client-code@x402/[email protected] dist/cjs/http/index.js:1620-1628 — no header and no v1 body throws "Invalid payment required response"
HTTP_ROUTE_DISCRIMINATES info hygiene the host tells a real path from an impossible one, so the declared path’s answer is evidence the route exists (runs only when the one-request negative control could be fetched)
  • field-reportx402-foundation/x402#3104 (Circadian-agent, 2026-08-21) — 533-host census of the live catalog: 493/526 answering hosts (93.9%) return 404/410 for an impossible path; 10 hosts (1.9%) answer 402 for everything, clustered on 5 domains — a platform property, not a per-endpoint mistake
  • field-reportx402-foundation/x402#3104 (mayonerajan, 2026-08-21) — models the outcome as route_existence: confirmed | uninformative rather than pass/fail, and adopts the negative control into the x402-doctor design
HTTP_SOFT_404 info hygiene an impossible path is not answered with a success status — a soft-404 host defeats liveness checks, a different class than the 402 gate (runs only when the negative control could be fetched)
  • field-reportx402-foundation/x402#3104 (Circadian-agent, 2026-08-21) — 13/526 hosts (2.5%) answer 200 for an impossible path, diffuse across 13 distinct domains: independent mistakes, not a platform
  • field-reportx402-foundation/x402#3104 (mayonerajan, 2026-08-21) — "it is not 'route exists', merely an indeterminate positive response"; reported distinctly from both 404/410 and 402-before-routing
UA_GATE_402 info hygiene the paid route answers every common agent client alike — a 403, 429 or challenge for some user-agents and not others is an edge bot wall between a buyer and the 402, and the buyers it turns away are exactly the scripted ones x402 is for (live lints only)
  • field-report10x402 house incident, 2026-08-19 → 2026-09-03 — Cloudflare Pages Browser Integrity Check answered 403 "error code: 1010" to Python-urllib/3.x on three house hosts for about two weeks while curl and node probes of the same paths passed; a Python-stdlib buyer could pay the 402 and could not read discovery
  • house-opinionAn edge bot wall is a zone-level toggle that refuses clients whose headers it reads as bot-like. Nothing in x402 makes a buyer a browser, so leaving it on in front of a paid route sells only to the client families the wall happens to like — and the seller cannot see it, because their own browser passes.
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling — the transport describes the request that elicits a 402 and states NO User-Agent requirement anywhere: this check rests on client reality, not on a MUST
UA_GATE_SURFACES info hygiene the discovery surface a buyer reads (llms.txt, openapi.json, .well-known/x402) answers every common agent client alike — a surface gated by user-agent is one an agent cannot read before it decides to pay (live lints only, against the first of those paths that exists)
  • field-report10x402 house incident, 2026-08-19 → 2026-09-03 — the gate was on the DISCOVERY paths while the paid route answered normally: the buyer could pay and could not read llms.txt or .well-known/x402
  • house-opinionDiscovery surfaces exist to be read by programs. Serving them only to clients that look like browsers is the same defect as publishing them and then not linking them, with the added cost that the agent gets a 403 it will read as your endpoint being down.
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling — no User-Agent requirement is stated for any x402 request, discovery included
ORIGIN_MARKER_PRESENT info hygiene every response this lint received carries the origin marker the seller advertised — a non-2xx without it was minted on the path (a bot wall, a WAF, a cache, a CDN error page) rather than by the origin, and is reported as edge-invented instead of as schema drift (runs only when a marker header is advertised in the 402 envelope or /.well-known/x402)
  • field-reportMoltbook b49c9347 (@prowlnetwork) — under load, 40% of the error shapes a caller sees are undocumented: not in the seller's schema, not in any spec, and not reproducible from the seller's own testing
  • field-reportMoltbook a6ed0b5d (@lobbyagent) — intermediaries MINT most of those shapes, and nobody has captured what the origin actually emitted: the comparison that would settle it is never made
  • livehouse incident, vault log 2026-09-03 § BIC — for two days every machine surface of the estate answered 403 from Cloudflare's Browser Integrity Check while the ORIGIN never saw the call. Every instrument pointed at the envelope; the envelope was fine and was never in the reply
  • house-opinionThe cheap discriminator needs no cooperation from the path: the origin sets a header nothing in front of it reproduces, and a non-2xx without it is the path talking. Only non-empty presence is asserted — the value is not a secret and nothing routes on it.
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling — the transport names no response header other than PAYMENT-REQUIRED and requires no origin marker anywhere: this is a house convention, cited so a reader can confirm the silence
x402 v2 envelope (the PAYMENT-REQUIRED header)46 checks
x402 v2 envelope (the PAYMENT-REQUIRED header): 46 checks
codeseverityregimewhat it checks, and where the rule comes from
V2_HEADER_PRESENT error bazaar a PAYMENT-REQUIRED response header is present (CDP will not index a v1-only 402)
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
  • cdp-validatorcdp-validator-toolshed.json preflight[6] payment_required_header (required)
  • client-code@x402/[email protected] dist/cjs/http/index.js:1620-1628 — the v2 client DOES fall back to a v1 body
  • field-reportx402-foundation/x402#3091 — [email protected] is still a live buyer population
V2_B64_URLSAFE errorcore payment the header is standard base64, not base64url
  • client-code@x402/[email protected] dist/cjs/utils/index.js:133 — Base64EncodedRegex = /^[A-Za-z0-9+/]*={0,2}$/
  • client-code@x402/[email protected] dist/cjs/http/index.js:1778-1781 — the regex is tested on the RAW header, then it throws, before any decode
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling — "Base64-encoded", SILENT on the alphabet
V2_B64_DECODE errorcore payment the header decodes as base64
  • client-code@x402/[email protected] dist/cjs/http/index.js:1781 — JSON.parse(safeBase64Decode(header)), uncaught
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
V2_JSON errorcore payment the decoded header is JSON
  • client-code@x402/[email protected] dist/cjs/http/index.js:1781 — a SyntaxError escapes decodePaymentRequiredHeader
  • specspecs/x402-specification-v2.md:72-107 § 5.1.1 JSON Payload
V2_VERSION errorcore payment the v2 payload declares x402Version 2
  • specspecs/x402-specification-v2.md:114 § 5.1.2 — x402Version Required, "must be 2"
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:111 — x402Version: z.literal(2), inside a discriminatedUnion
  • field-reportx402-foundation/x402#3045 wire-format bug 1 — a v1-shaped challenge on a v2 resource
V2_ACCEPTS_NONEMPTY errorcore payment accepts[] is a non-empty array
  • specspecs/x402-specification-v2.md:117 § 5.1.2 — accepts Required
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:114 — accepts: z.array(PaymentRequirementsV2Schema).min(1)
  • cdp-validatorcdp-validator-toolshed.json preflight[7] has_accepts (required)
V2_SCHEME errorcore payment each accept names a scheme
  • specspecs/x402-specification-v2.md:120-131 § 5.1.2 (PaymentRequirements table)
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:102 — scheme: NonEmptyString
V2_SCHEME_KNOWN info payment the scheme has a published specification (v2 leaves the field open, so this is an info)
  • specspecs/schemes/ — exact, upto, batch-settlement, auth-capture each have a scheme document
  • cdp-validatorcdp-validator-toolshed.json preflight[8] accepts[0].scheme, expected "exact or upto"
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:102 — the v2 schema accepts any non-empty string, by design
V2_NETWORK_CAIP2 errorcore payment network contains a colon (the client’s rule), and is not a v1 plain name
  • specspecs/x402-specification-v2.md:125 § 5.1.2 — network Required, CAIP-2 format
  • specspecs/x402-specification-v2.md:616-621 § 11.1 Network Identifiers
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:63-65 — NetworkSchemaV2 = z.string().min(3).refine(v => v.includes(":"))
V2_NETWORK_CAIP2_STYLE info hygiene the network string is CAIP-2 shaped (3–8 character namespace) — a style note, not a client rule
  • specspecs/x402-specification-v2.md:616-621 § 11.1 — "Networks in x402 v2 use CAIP-2 format"
  • house-opinionno shipping client bounds the namespace; @x402/core requires only min(3) and a colon, so this may only be an info
V2_NAMESPACE_KNOWN info hygiene the report says so when a network namespace was checked structurally rather than deeply
  • specspecs/x402-specification-v2.md:616-621 § 11.1 — namespaces are open-ended; "ach:us" and "sepa:eu" are given as examples
  • specspecs/schemes/batch-settlement/scheme_batch_settlement_cloudflare.md:7 — cloudflare:402 is a real network with its own scheme document
  • house-opinionworker/lint.js addressFamily() — eip155 and solana are the namespaces whose address formats this linter knows; everything else is checked structurally, and the report says which
V2_NETWORK_SUPPORTED error bazaar the eip155 chain is one CDP’s facilitator settles on
  • cdp-validatorcdp-validator-toolshed.json preflight[9] accepts[0].network, expected "a facilitator-supported network (Base, Solana, Polygon, Arbitrum, World)"
  • house-opiniona chain outside that set is legal x402 and payable through a self-hosted facilitator — it is CDP indexing that is lost, not payment
V2_AMOUNT errorcore payment the price is in amount, not the v1 maxAmountRequired
  • specspecs/x402-specification-v2.md:120-131 § 5.1.2 (PaymentRequirements table)
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:104 — amount: NonEmptyString; maxAmountRequired is not a v2 key
V2_AMOUNT_ATOMIC errorcore payment the amount is a string of atomic units
  • specspecs/x402-specification-v2.md:120-131 § 5.1.2 (PaymentRequirements table)
  • client-code@x402/[email protected] dist/cjs/index.js:570 — BigInt(authorization.value); BigInt("0.01") throws
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:433,440 — the reference facilitator schema refines on isInteger
V2_AMOUNT_MINIMUM error bazaar the amount clears CDP’s 1000-atomic-unit ($0.001) indexing floor
  • cdp-validatorcdp-validator-toolshed.json preflight[11] accepts[0].amount (required), expected ">= 1000"
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:104 — the client itself applies no numeric bound, so the facilitator is the only enforcer
V2_INDEX_AMOUNT error bazaar the amount is a value CDP’s required amount preflight can read at all
  • cdp-validatorcdp-validator-toolshed.json preflight[11] accepts[0].amount (required), expected ">= 1000" — the check compares a value, so an absent or non-integer amount cannot satisfy it
V2_INDEX_TIMEOUT error bazaar maxTimeoutSeconds is SET, which is what CDP’s required preflight asks
  • cdp-validatorcdp-validator-toolshed.json preflight[13] accepts[0].maxTimeoutSeconds (required) — "maxTimeoutSeconds is set". Presence is the whole of the provider’s stated rule; the JSON type is a payment question and V2_MAX_TIMEOUT asks it
V2_INDEX_ASSET error bazaar asset identifies a token on a chain CDP settles, so its required asset preflight can pass
  • cdp-validatorcdp-validator-toolshed.json preflight[10] accepts[0].asset (required) — "Asset is USDC", captured with a token contract address as the actual value
  • specspecs/x402-specification-v2.md:127 § 5.1.2 — on a chain, asset is the token contract address; a ticker cannot be the token CDP looks up
V2_INDEX_PAYTO error bazaar payTo is an address, which is what CDP’s required payee preflight asks for
  • cdp-validatorcdp-validator-toolshed.json preflight[12] accepts[0].payTo (required) — "payTo address present", captured with a string address as the actual value
V2_PAYTO errorcore payment payTo has the address shape its network’s namespace requires
  • specspecs/x402-specification-v2.md:128 § 5.1.2 — "Recipient wallet address or role constant (e.g., \"merchant\")"
  • specspecs/schemes/exact/scheme_exact_svm.md:53-68 — a base58 payTo on solana:*
  • client-code@x402/[email protected] dist/cjs/index.js:537 — `to: getAddress(paymentRequirements.payTo)`; viem throws on a non-address
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:106 — payTo: NonEmptyString, i.e. the shape rule is the scheme’s, not the envelope’s
V2_ASSET errorcore payment asset names the token in the form its network’s namespace requires
  • specspecs/x402-specification-v2.md:127 § 5.1.2 — "Token contract address or ISO 4217 currency code for fiat"
  • client-code@x402/[email protected] dist/cjs/index.js:565 — verifyingContract: getAddress(requirements.asset)
  • specspecs/schemes/exact/scheme_exact_svm.md:71 — asset is the token mint public key
V2_MAX_TIMEOUT errorcore payment maxTimeoutSeconds is a positive JSON number (a string "60" is not one)
  • specspecs/x402-specification-v2.md:129 § 5.1.2 — maxTimeoutSeconds, type number, Required
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:107 — maxTimeoutSeconds: z.number().positive(), required, no coercion
  • client-code@x402/[email protected] dist/cjs/index.js:539 — validBefore is computed from it; undefined yields BigInt("NaN"), which throws
  • cdp-validatorcdp-validator-toolshed.json preflight[13] accepts[0].maxTimeoutSeconds (required)
V2_EXTRA_EIP712 error payment extra.{name,version} is present on an eip3009 exact entry, where the client signs from it
  • specspecs/schemes/exact/scheme_exact_evm.md:72-73 — extra.name and extra.version, both "(required)"
  • specspecs/schemes/exact/scheme_exact_evm.md:171-172,285-286 — conditional under permit2, optional under erc7710
  • client-code@x402/[email protected] dist/cjs/index.js:555-558 — signEIP3009Authorization throws when either is absent
  • client-code@x402/[email protected] dist/cjs/index.js:1261 — assetTransferMethod defaults to "eip3009"
V2_ACCEPTS_V1_FIELDS warn payment the accept carries no v1-only fields
  • specspecs/x402-specification-v2.md:120-131 § 5.1.2 (PaymentRequirements table)
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:101-109 — a plain z.object, so unknown keys are STRIPPED on any re-parse
  • client-code@x402/[email protected] dist/esm/client/index.mjs:262 — the raw entry is echoed as `accepted`, unstripped
V2_RESOURCE_OBJECT errorcore payment resource is the v2 object, not a v1 flat string
  • specspecs/x402-specification-v2.md:116 § 5.1.2 — resource Required, ResourceInfo object
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:113 — resource: ResourceInfoSchema
  • cdp-validatorcdp-validator-toolshed.json preflight[14] has_resource (required)
V2_RESOURCE_URL_PARSES warn payment resource.url parses as a URL at all — it is echoed into the payment payload
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:69 — url: NonEmptyString, so the client will happily carry a bare path
  • client-code@x402/[email protected] dist/cjs/client/index.js:413 — resource is copied verbatim into the outgoing PaymentPayload, which is what a settlement is attributed to
  • field-reportx402-foundation/x402#3045 wire-format bug 3 — "resource.url must be absolute, not a bare path"
V2_RESOURCE_URL error bazaar resource.url is an absolute https URL
  • cdp-validatorcdp-validator-toolshed.json preflight[0] url_valid and preflight[1] url_https, both required
  • specspecs/x402-specification-v2.md:132-141 § 5.1.2 (ResourceInfo table)
  • field-reportx402-foundation/x402#3045 wire-format bug 3
V2_RESOURCE_METHOD warn bazaar resource.method, when published, agrees with bazaar.info.input.method
  • specspecs/x402-specification-v2.md:132-141 § 5.1.2 (ResourceInfo table) — there is no `method` member, so its absence is conformant
  • specspecs/extensions/bazaar.md:251-269 — info.input.method is the declared verb
  • livecdp-validator-toolshed.json paymentRequirements.resource.method — indexed sellers do publish it
V2_RESOURCE_DESCRIPTION error bazaar resource.description is under 500 characters (absent is an info; over the limit is an error)
  • specspecs/x402-specification-v2.md:132-141 § 5.1.2 (ResourceInfo table) — description Optional
  • cdp-docshttps://docs.cdp.coinbase.com/x402/seller/get-discovered — "the CDP Facilitator rejects verify and settle requests whose description exceeds that limit" (500 characters)
V2_RESOURCE_MIMETYPE info hygiene resource.mimeType, when published, looks like a media type
  • specspecs/x402-specification-v2.md:132-141 § 5.1.2 (ResourceInfo table) — mimeType Optional
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:71 — mimeType: z.string().nullish()
V2_RESOURCE_URL_MATCHES info hygiene resource.url is the URL that was called
  • client-code@x402/[email protected] dist/cjs/client/index.js:413 — settlement is attributed to the echoed resource
  • house-opiniona proxy, a route template or a canonicalised host makes a mismatch legitimate, so this may only ever be an info
V2_SERVICE_NAME warn bazaar resource.serviceName, when published, is ≤32 printable-ASCII characters (absence is silent)
  • specspecs/extensions/bazaar.md:389 — "length ≤ 32 characters"; on violation, "Drop the field."
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:72 — z.string().min(1).max(32).regex(/^[\x20-\x7e]+$/)
V2_TAGS warn bazaar resource.tags, when published, are ≤5 entries of ≤32 printable-ASCII characters (absence is silent)
  • specspecs/extensions/bazaar.md:390 — "at most 5 entries; each entry non-empty, printable ASCII … length ≤ 32"
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:73 — z.array(z.string().min(1).max(32).regex(PRINTABLE_ASCII)).max(5)
V2_BAZAAR_PRESENT error bazaar extensions.bazaar is present — in v2 its presence IS the discovery opt-in
  • cdp-validatorcdp-validator-toolshed.json preflight[15] has_bazaar_extension (required)
  • specspecs/extensions/bazaar.md:512-517 § Client Behavior — omitting the extension means no cataloging
  • field-reportx402-foundation/x402#3045 — a CDP engineer: `extensions.bazaar.discoverable` is "not a valid field"
V2_BAZAAR_INFO error bazaar extensions.bazaar.info is present
  • specspecs/x402-specification-v2.md:143-149 § 5.1.2 (Extensions table) — info Required
  • cdp-validatorcdp-validator-toolshed.json preflight[16] bazaar.info (required)
V2_BAZAAR_SCHEMA error bazaar extensions.bazaar.schema is present
  • specspecs/x402-specification-v2.md:143-149 § 5.1.2 (Extensions table) — schema Required
  • specspecs/extensions/bazaar.md:322 — "Facilitators must validate info against schema before cataloging"
  • cdp-validatorcdp-validator-toolshed.json preflight[23] bazaar.schema (required)
V2_BAZAAR_SCHEMA_CONTENT error bazaar the bazaar schema meets its own content MUSTs: requires input, and every $ref/$id is same-document
  • specspecs/extensions/bazaar.md:313-322 § Schema Validation — Draft 2020-12, "Must define an input property (required)", and "$ref and $id values must be same-document JSON Pointer fragments (starting with #); external references … are not allowed"
  • field-reportx402-foundation/x402#3045 wire-format bug 5 — an external $ref broke CDP’s validator outright
V2_BAZAAR_INFO_VALIDATES error bazaar info validates against its own schema
  • specspecs/extensions/bazaar.md:322 — facilitators MUST validate info against schema before cataloging
  • cdp-validatorcdp-validator-toolshed.json preflight[24] parse (required)
  • field-reportx402-foundation/x402#3045 — an info/schema mismatch is declined silently; nothing reaches the seller’s logs
V2_BAZAAR_BAG_MISMATCH error bazaar every key a schema parameter-bag requires is supplied by info in THAT bag, not a sibling bag
  • specspecs/extensions/bazaar.md:322 — the info/schema validation this contradiction is guaranteed to fail
  • field-reportx402-foundation/x402#3104 (Circadian-agent, 2026-08-20) — 276 of 14,691 live listings with a `required` key fail their own schema, across 59 hosts; the named specimen requires `name` in queryParams while its own example supplies it in pathParams
  • field-reportCircadian-agent/agent-economy-data findings/bazaar-info-fails-own-schema-2026-08-20.md — the census behind those counts
V2_BAZAAR_INPUT error bazaar bazaar.info.input carries a worked sample call
  • specspecs/extensions/bazaar.md:245-282 § Discovery Info Structure — input is Required in every discriminant
  • cdp-validatorcdp-validator-toolshed.json preflight[17] bazaar.info.input (required)
V2_BAZAAR_INPUT_TYPE error bazaar bazaar.info.input.type is the "http" or "mcp" discriminator, with that branch’s required fields
  • specspecs/extensions/bazaar.md:251-282 — type Required ("http"/"mcp"); bodyType and body Required for POST/PUT/PATCH; toolName and inputSchema Required for mcp
  • cdp-validatorcdp-validator-toolshed.json preflight[18] bazaar.info.input.type (required)
  • field-reportx402-foundation/x402#3045 wire-format bug 4 — the missing `type` discriminator
V2_BAZAAR_INPUT_METHOD error bazaar bazaar.info.input.method is an HTTP verb from the spec’s enums, and matches the verb that was probed
  • specspecs/extensions/bazaar.md:251-269 — method Required, one of GET/HEAD/DELETE or POST/PUT/PATCH
  • cdp-validatorcdp-validator-toolshed.json preflight[19] bazaar.info.input.method and preflight[20] bazaar.info.input.method.matches_request, both required
V2_BAZAAR_OUTPUT_TYPE warn bazaar bazaar.info.output, when published, carries its Required type
  • specspecs/extensions/bazaar.md:284-294 § Output Types — output optional; within it, type Required
  • cdp-validatorcdp-validator-toolshed.json preflight[21] bazaar.info.output (advisory)
V2_BAZAAR_OUTPUT_EXAMPLE info bazaar bazaar.info.output.example is a computed response — any JSON value, and CDP grades it advisory
  • specspecs/extensions/bazaar.md:284-294 — the example row is `example | any | No`
  • specspecs/extensions/bazaar.md:46-53 — the spec’s own GET example gives output.example as an OBJECT
  • cdp-validatorcdp-validator-toolshed.json preflight[22] bazaar.info.output.example (advisory)
V2_BAZAAR_INPUT_REPLAYABLE info hygiene a POST input that declares a JSON body (bodyType "json", or a body given as an object or array) gives it as a JSON object or array of at most 16 KB, so a buyer replaying the declaration verbatim sends that body rather than {} — a body declared as "text" or form-data is outside this check, including text that parses as JSON
  • field-reportworker/catalog.js ENDPOINTS note (vault-v0mjp) — vet402 replays a seller's declared body only when it is a JSON object or array and otherwise sends `{}`; every one of its eight paid attempts on 10x402 (2026-09-02..17) sent `{}` and was refused 400, against a body declared as a JSON string under bodyType "text"
  • field-reportvet402 observatory, L1 purchase ledger (CC BY 4.0), https://vet402.com/api/v1/observatory/export.csv — request_body records `declared` vs `empty` per paid attempt; replay of the declared body began 2026-09-17 (scripts/observer-ledger-leads.mjs EMPTY_BODY_SINCE)
  • house-opinionthe 16 KB bound: a worked example is one sample call, and a declaration past 16 KB is a payload a replaying buyer should not be asked to send blind
  • specspecs/extensions/bazaar.md:251-269 — bodyType is one of json, form-data, text; the spec admits a string body
V2_BAZAAR_INPUT_QUERY_EXAMPLE info hygiene a GET input declares example queryParams — {} when the call takes none — so a buyer replaying the declaration knows what URL to call
  • house-opinionthe same replay construction as V2_BAZAAR_INPUT_REPLAYABLE, for the query-parameter family: a buyer that builds the call from the declaration alone sends the bare resource URL when queryParams is absent, and cannot tell "takes none" from "forgot to say"
  • specspecs/extensions/bazaar.md:46-53 — the spec's own GET example declares queryParams
  • specspecs/extensions/bazaar.md:251-269 — queryParams is optional on the query-method shape
x402 v1 envelope (the 402 body)21 checks
x402 v1 envelope (the 402 body): 21 checks
codeseverityregimewhat it checks, and where the rule comes from
V1_ABSENT info payment a v1 body envelope is published alongside the v2 header
  • client-code[email protected] dist/esm/index.mjs:22 — the v1 client reads the body and never looks at PAYMENT-REQUIRED
  • field-reportx402-foundation/x402#3091 — the pre-header buyer population is real and shrinking
  • cdp-validatorcdp-validator-toolshed.json preflight[4] valid_json (required) — an EMPTY 402 body fails it, so serve at least `{}`
V1_BODY_NOT_ENVELOPE info payment the 402 body is a v1 envelope or is empty, not something a v1 client will misread
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
  • specspecs/transports-v2/http.md:19-25 — the spec’s own 402 example serves a body of `{}`
  • client-code[email protected] dist/esm/index.mjs:22-23 — an error blob makes accepts undefined and .map throws
  • cdp-validatorcdp-validator-toolshed.json preflight[4] valid_json (required) — the body is parsed as JSON during indexing
V1_BODY_PRESENT warn payment a v1 envelope is published in the 402 body
  • specspecs/transports-v1/http.md § Payment Required Signaling
  • house-opiniononly fires when nothing was published in either transport; ENVELOPE_PRESENT carries the core error for that case
V1_BODY_JSON errorcore payment the 402 body parses as JSON
  • specspecs/x402-specification-v1.md § 5.1.1 JSON Payload
  • client-code[email protected] dist/esm/index.mjs:22 — response.json() with no try/catch
V1_VERSION errorcore payment the body declares x402Version 1
  • specspecs/x402-specification-v1.md:99-108 § 5.1.2
  • client-code@x402/[email protected] dist/cjs/http/index.js:1625 — the body fallback requires x402Version === 1 exactly
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:388 — x402Versions = [1]
V1_ACCEPTS_NONEMPTY errorcore payment accepts[] is a non-empty array
  • specspecs/x402-specification-v1.md:99-108 § 5.1.2 — accepts Required
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:93 — accepts: z.array(PaymentRequirementsV1Schema).min(1)
  • client-code[email protected] dist/esm/index.mjs:23 — accepts.map throws when accepts is absent
V1_SCHEME errorcore payment each accept names a scheme
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:438 — scheme: z.enum(schemes)
V1_SCHEME_KNOWN error payment the v1 scheme is exact — v1’s enum is closed where v2’s is open
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:387 — var schemes = ["exact"]
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:438 — scheme: z3.enum(schemes), applied per accepts entry
  • client-code[email protected] dist/esm/index.mjs:22-23 (response.json(), then PaymentRequirementsSchema.parse per entry)
V1_MAX_AMOUNT_REQUIRED errorcore payment the price is in maxAmountRequired, not the v2 amount
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:440 — maxAmountRequired is required; `amount` is not a v1 key
V1_AMOUNT_ATOMIC errorcore payment maxAmountRequired is a string of atomic units
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:433,440 — z3.string().refine(isInteger)
  • client-code[email protected] dist/esm/index.mjs:30 — BigInt(maxAmountRequired) throws on a non-digit string
V1_NETWORK_NAME errorcore payment network is a v1 plain name, not CAIP-2
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:16-34 — NetworkSchema is a z.enum of plain names; no member contains a colon
V1_NETWORK_KNOWN error payment the v1 network name is one of the seventeen the dominant v1 client’s enum admits
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:16-34 — the closed z.enum: abstract, abstract-testnet, base-sepolia, base, avalanche-fuji, avalanche, iotex, solana-devnet, solana, sei, sei-testnet, polygon, polygon-amoy, peaq, story, educhain, skale-base-sepolia
  • client-code[email protected] dist/esm/index.mjs:22-23 (response.json(), then PaymentRequirementsSchema.parse per entry)
  • house-opinion@x402/[email protected]'s v1-compatibility schema is looser (@x402/[email protected] dist/cjs/schemas/index.js:62, NonEmptyString), so this is a claim about the dominant v1 client rather than about every parser — hence error, not core
V1_RESOURCE_STRING errorcore payment resource is a flat, absolute URL string, not the v2 object
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:441 — resource: z3.string().url(), so a bare path is a hard ZodError
  • field-reportx402-foundation/x402#3045 wire-format bug 3, in its v1 spelling
V1_PAYTO errorcore payment payTo has the address shape its v1 network requires (EVM 0x, or base58 on solana)
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:435 — EvmOrSvmAddress = EvmAddressRegex.or(SvmAddressRegex)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:16-34 — the enum includes solana and solana-devnet
V1_ASSET errorcore payment asset names the token in the form its v1 network requires
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table)
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:436,447 — asset: mixedAddressOrSvmAddress
  • client-code[email protected] dist/esm/chunk-EJI6X7BV.mjs:75 — verifyingContract: getAddress(asset), which throws on a ticker
V1_MIMETYPE error payment mimeType is present (spec: Optional — but the dominant v1 client’s schema requires it)
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table) — mimeType Optional
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:443 — mimeType: z3.string(), NOT .optional()
  • client-code[email protected] dist/esm/index.mjs:22-23 (response.json(), then PaymentRequirementsSchema.parse per entry)
  • house-opinion@x402/[email protected]'s v1 schema does make it optional (@x402/[email protected] dist/cjs/schemas/index.js:83) — the two v1 parsers disagree
V1_DESCRIPTION error payment description is present (missing is an error; present-but-empty is a warn)
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table) — description Required
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:442 — description: z3.string(), required
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:82 — required in the modern v1-compatibility schema too
V1_MAX_TIMEOUT errorcore payment maxTimeoutSeconds is a positive integer JSON number
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table) — maxTimeoutSeconds, type number, Required
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:446 — z3.number().int(), so the string "60" is a ZodError
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:86 — z.number().positive(), required
V1_EXTRA_EIP712 error payment extra carries the EIP-712 domain the v1 client signs over (EVM networks only)
  • specspecs/schemes/exact/scheme_exact_evm.md:72-73 — extra.name and extra.version required for eip3009
  • client-code[email protected] dist/esm/chunk-EJI6X7BV.mjs:65-76 — signAuthorization reads extra?.name and extra?.version straight into the typed-data domain, with no fallback
  • house-opinionthe reference facilitator recomputes the domain from its own table, so the mismatch surfaces only as invalid_exact_evm_payload_signature
  • specspecs/x402-specification-v1.md:261 — v1 Solana exact uses TransferChecked, which has no EIP-712 domain
V1_OUTPUT_SCHEMA warn bazaar outputSchema is present for v1 discovery
  • specspecs/x402-specification-v1.md:110-124 § 5.1.2 (PaymentRequirements table) — outputSchema Optional
  • specspecs/extensions/bazaar.md:577+ § Backwards Compatibility — v1 discovery rode on outputSchema
  • field-reportx402-foundation/x402#2844 — indexing began after the metadata moved there
V1_DISCOVERABLE info bazaar outputSchema.input.discoverable is an opt-OUT — absence means indexed
  • client-codex402-foundation/x402 go/extensions/v1/facilitator.go (main, read 2026-08-19) — "// Check if discoverable (default to true if not specified)" followed by `discoverable := true`, then an override only when the key is present
  • liveworker/positive-control.js — a live indexed seller nests the flag under outputSchema.input
  • cdp-docshttps://docs.cdp.coinbase.com/x402/bazaar — v1 discovery data reads input.discoverable
Dual-stack consistency5 checks
Dual-stack consistency: 5 checks
codeseverityregimewhat it checks, and where the rule comes from
DUAL_PAYTO errorcore payment matched offers pay the same address
  • house-opinionworker/lint.js — two views of one offer; divergence means half the revenue lands elsewhere
  • client-code@x402/[email protected] dist/cjs/index.js:568 — getAddress is case-insensitive, so the comparison is too
DUAL_PRICE errorcore payment matched offers quote the same price
  • house-opinionworker/lint.js — one offer must not carry two prices; matched on (chain, asset) so different-decimal assets are not compared
DUAL_NETWORK errorcore payment the two envelopes offer overlapping chains
  • house-opinionworker/lint.js — a payment signed on one chain is worthless on the other
  • client-code[email protected] dist/esm/chunk-V3RMM5AE.mjs:52-70 — the client’s own EvmNetworkToChainId map, which is the two spellings of one chain
DUAL_ASSET errorcore payment matched offers name the same asset
  • house-opinionworker/lint.js — different assets means the two versions are selling for different money
DUAL_RESOURCE warn payment both versions name the same resource URL
  • house-opinionworker/lint.js — two URLs split one endpoint’s settlement record across two listings
  • field-reportx402-foundation/x402#3045 — discovery keys on the resource URL
Version-detection hygiene2 checks
Version-detection hygiene: 2 checks
codeseverityregimewhat it checks, and where the rule comes from
VERSION_HEADER_SAYS_V1 error payment the PAYMENT-REQUIRED header does not carry a v1 payload
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
  • client-code@x402/[email protected] dist/cjs/schemas/index.js:128-131 — PaymentRequired is a discriminatedUnion on x402Version, so a v1 payload in the header is legally parsed AS v1
  • client-code@x402/[email protected] dist/esm/client/index.mjs:219 — registeredClientSchemes.get(paymentRequired.x402Version): dispatch is on the PAYLOAD’s version, and a v1 client answers with X-PAYMENT while a v2 server reads PAYMENT-SIGNATURE
VERSION_BODY_SAYS_V2 warn payment the 402 body does not carry a v2 payload (a core error when no valid v2 header is published)
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
  • client-code@x402/[email protected] dist/cjs/http/index.js:1620-1628 — the header wins whenever present; the body fallback accepts only x402Version === 1
  • client-code[email protected] dist/esm/index.mjs:22-23 — a v1 client reads the body with v1 rules whatever it declares
Disclosed billing terms1 checks
Disclosed billing terms: 1 checks
codeseverityregimewhat it checks, and where the rule comes from
BILLING_TERMS_DISCLOSED info informational the four billing terms buyers ask for — minimum billable unit, what a hold covers, who observes acceptance, how long silence counts as acceptance — are published as a machine-readable object (informational: no spec requires this, so it never grades)
  • field-reportMoltbook 92cd240a… (bitroadai), "transport success is not billable completion" — buyers asked for the minimum billable unit and what a hold covers
  • field-reportMoltbook 6868a451… / 2d2df453… (relayzero, @miacollective) — who observes acceptance, and how long silence counts as acceptance, as terms a buyer needs before paying
  • house-opinionno spec requires these four, no client parses them, no registry demands them — this check can only REPORT. Key names from house PR algonormative/10x402#10
  • specspecs/x402-specification-v2.md § 6.1 (Payment Flow Models), read 2026-09-09 — SILENT on all four
The paid response body4 checks
The paid response body: 4 checks
codeseverityregimewhat it checks, and where the rule comes from
EMPTY_BODY_200 info hygiene a paid 2xx carries a body at all — an empty or whitespace-only answer is a charge with no product, and is indistinguishable from a legitimate "no match" (live lints only — runs only when a paid observation is attached)
  • livepenny402 launch night, 2026-09-02 (vault log/2026/09/2026-09-02.md § "penny402 launch night") — inspecting bodies, not statuses, found the muse tier’s pinned model (mistral-nemo) timing out about 1 in 4 and returning token salad under HTTP 200; 10x402 lint graded the same endpoints A / bazaar_ready. Re-pinned with a gate, redraw and retries; post-fix paid check 6/6.
  • field-reportmoltbook c6e9a0b6 (@clawdsmith) — a false-miss rate is unmeasurable once "wrong answer" and "no match" share an empty body
  • house-opinionAn answer body is the product on a paid call. A seller who returns nothing has charged for nothing, and has also destroyed the buyer's ability to measure how often the service misses — because the miss and the outage are the same bytes.
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
UNPARSEABLE_BODY_200 info hygiene a paid 2xx that declares a JSON content-type parses as JSON — runs only for a JSON content-type and a non-empty, unclipped body (live lints only — runs only when a paid observation is attached)
  • livepenny402 launch night, 2026-09-02 (vault log/2026/09/2026-09-02.md § "penny402 launch night") — inspecting bodies, not statuses, found the muse tier’s pinned model (mistral-nemo) timing out about 1 in 4 and returning token salad under HTTP 200; 10x402 lint graded the same endpoints A / bazaar_ready. Re-pinned with a gate, redraw and retries; post-fix paid check 6/6.
  • field-reportmoltbook d261592d / 4c614192 (@smokeinthedesert) — "everything proves the write, nothing proves the read": receipts prove settlement, nothing proves the served body was an answer
  • house-opinionA content-type is a promise about how the bytes may be read. A buyer that has already paid cannot renegotiate it, so a body that does not parse under its own declared type is an unusable purchase served with a 200.
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
SCHEMA_SHAPE_200 info hygiene a paid 2xx matches the output shape the SKU itself declares (extensions.bazaar.info.output, or v1 outputSchema.output) — runs only when that declaration is an actual schema (live lints only — runs only when a paid observation is attached)
  • livepenny402 launch night, 2026-09-02 (vault log/2026/09/2026-09-02.md § "penny402 launch night") — inspecting bodies, not statuses, found the muse tier’s pinned model (mistral-nemo) timing out about 1 in 4 and returning token salad under HTTP 200; 10x402 lint graded the same endpoints A / bazaar_ready. Re-pinned with a gate, redraw and retries; post-fix paid check 6/6.
  • field-reportmoltbook d261592d / 4c614192 (@smokeinthedesert) — "everything proves the write, nothing proves the read": receipts prove settlement, nothing proves the served body was an answer
  • house-opinionThe output shape published in discovery is what an agent planned its call around. A served body that does not match it is a contract the seller wrote and then broke, and the buyer finds out after paying.
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
DEGENERATE_TEXT_200 info hygiene a paid text answer is not degenerate — a HEURISTIC over token diversity, consecutive repetition and a mid-token cut, capped at warn if ever graded, info today (live lints only — runs only when a paid observation is attached)
  • livepenny402 launch night, 2026-09-02 (vault log/2026/09/2026-09-02.md § "penny402 launch night") — inspecting bodies, not statuses, found the muse tier’s pinned model (mistral-nemo) timing out about 1 in 4 and returning token salad under HTTP 200; 10x402 lint graded the same endpoints A / bazaar_ready. Re-pinned with a gate, redraw and retries; post-fix paid check 6/6.
  • field-reportmoltbook e1bb08bb (@pressuretestagent) — receipts cannot split rejected-but-charged from settled-with-effect
  • house-opinionToken salad under a 200 is what a timed-out or truncated generation looks like from the buyer's side. The thresholds below are house opinion and are named as constants so a reader can disagree with a number rather than with a verdict.
  • specspecs/transports-v2/http.md:172-174 § Response Body ("Response bodies are a server implementation concern")
Cross-route consistency2 checks
Cross-route consistency: 2 checks
codeseverityregimewhat it checks, and where the rule comes from
CROSS_ROUTE_CONSISTENCY info hygiene the routes that publish this resource agree about it — price, payTo, network and the description, compared against the 402 envelope, with a description that is a strict prefix of another route's reported as TRUNCATED rather than as a mismatch (live lints only — runs only when route observations are attached)
  • field-reportmoltbook 21b29f40 (@mayalaran) — every listing route on one API served a 500-character prefix of the description with no truncation marker, while the canonical route served the whole body: the two disagreed and nothing told a reader which was the offer
  • field-reportmoltbook 3edaffd8 (@lobbyagent) — the same observation framed as a parser differential: two readers of one resource reach two different answers, and neither is wrong about what it read. Nobody in that thread could check it for themselves
  • house-opinionA seller publishes one resource through several surfaces and a buyer agent reads whichever it found first. The envelope is the only one a payment is signed against, so every other surface that disagrees with it is a call planned against terms that will not be honoured — and the seller cannot see it, because they read their own canonical route.
  • specspecs/transports-v2/http.md:7-25 § Payment Required Signaling
LISTED_PRICE_MATCHES info hygiene the price the 402 quotes to a bare probe equals the price the seller lists for the resource in /.well-known/x402 (per rail) and in openapi.json's x-x402 prices (runs only when a discovery document is supplied)
  • field-reportvet402 L0 `price_mismatch` — the condition the estate improve loop recorded against an observer-unbuyable seller: the quote a bare probe receives differs from the listed price (vault-1tv7o, cycle 0, 2026-09-28)
  • house-opinionthe envelope is the only surface a payment is signed against, so a listing that disagrees with it is a price a buyer planned against and will not be charged — and an observer that compares the two stops before paying
The report’s own bounds2 checks
The report’s own bounds: 2 checks
codeseverityregimewhat it checks, and where the rule comes from
ACCEPTS_TRUNCATED info hygiene at most 8 accepts[] entries are linted per envelope
  • house-opinionworker/lint.js MAX_ACCEPTS_LINTED = 8
FINDINGS_TRUNCATED info hygiene this report is complete — no bound clipped it
  • house-opinionworker/lint.js — MAX_FINDINGS = 200, MAX_ACCEPTS_LINTED = 8, and the caller's body byte cap

x402 discovery and migration FAQ

Why does my x402 endpoint pass validate but not get indexed?

Base envelope validation and discovery are different layers. Bazaar metadata can be missing or fail its own schema, discoverable can be in the wrong place, or the unauthenticated probe can receive something other than a 402. 10x402 checks those technical blockers, but it cannot confirm whether Bazaar has crawled or approved a URL.

Why is my x402 service not showing up in Bazaar?

Check the HTTP response and the discovery metadata together: extensions.bazaar, the info-to-schema match, the v1 discoverable flag, and the status returned to an unpaid probe. A conformant response removes common listing blockers; it does not guarantee a listing.

What should I check during an x402 v1 vs v2 migration?

Check the version-specific network spelling, price field, resource shape, header encoding, and EIP-712 extra fields. If both versions are published, also check that payTo, price, chain, asset, and resource agree.

What is on the x402 conformance checklist?

94 published checks: HTTP behavior, x402 v1 and v2 envelopes, dual-stack consistency, version hygiene, Bazaar discovery metadata, disclosed billing terms, and two report safeguards that disclose truncation. Every finding includes a specific fix.

Why is my x402 endpoint not discoverable?

Discoverability depends on more than returning status 402. The response must publish readable payment terms and the discovery fields expected by the indexer. 10x402 can identify response-level blockers; it cannot measure demand or inspect the index itself.

Can I check just one thing instead of buying the whole report?

Yes. POST /lint/one and POST /lint/envelope/one answer about exactly one check id you name — for settling a single question like "is my v2 header base64url" without buying the catalogue. A full 94-check report costs 6.667x one check on a live URL and 10x on a pasted response — a 14.1x and 9.4x per-check advantage. Singles stay the cheaper buy through 6 questions live and 9 pasted; past that, buy the report. A single-check answer distinguishes three outcomes: it passed, it failed with the fix attached, or it did not apply to this response at all — which is not a pass and is never reported as one.

Does 10x402 store my URL, envelope, or report?

No linted URL, pasted envelope, or report is persisted in the application store. It retains aggregate lint results plus the quota and payment records needed to operate the service. What you lint is your business.

Limits, stated plainly