<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ScrapeMole</title>
    <description>The latest articles on DEV Community by ScrapeMole (@scrapemole).</description>
    <link>https://dev.to/scrapemole</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4177660%2Fbb656b97-0a39-4e2e-9602-b0be53c3a913.png</url>
      <title>DEV Community: ScrapeMole</title>
      <link>https://dev.to/scrapemole</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scrapemole"/>
    <language>en</language>
    <item>
      <title>Your x402 endpoint settles but isn't in the Bazaar? The buyer's client decides (and a no-money fix)</title>
      <dc:creator>ScrapeMole</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:49:25 +0000</pubDate>
      <link>https://dev.to/scrapemole/your-x402-endpoint-settles-but-isnt-in-the-bazaar-the-buyers-client-decides-and-a-no-money-fix-e1n</link>
      <guid>https://dev.to/scrapemole/your-x402-endpoint-settles-but-isnt-in-the-bazaar-the-buyers-client-decides-and-a-no-money-fix-e1n</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I run &lt;a href="https://scrapemole.pages.dev" rel="noopener noreferrer"&gt;ScrapeMole&lt;/a&gt;, a small set of pay-per-call x402 APIs. This post is about a discovery bug we hit in our own setup; the fix works for any x402 seller.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Our endpoints had been live on Base mainnet for about a day. Real outside wallets had paid for 18 calls, every one settled through the PayAI facilitator, and every 402 response carried a valid &lt;code&gt;extensions.bazaar&lt;/code&gt; declaration.&lt;/p&gt;

&lt;p&gt;Yet when we queried the facilitator's public catalog for our receive address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://facilitator.payai.network/discovery/resources?payTo=0xYOUR_PAY_TO&amp;amp;limit=100"&lt;/span&gt;
&lt;span class="c"&gt;# {"items":[], "pagination":{"total":0}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero. Meanwhile our &lt;strong&gt;testnet&lt;/strong&gt; endpoints &lt;em&gt;were&lt;/em&gt; in the catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why: the catalog indexes what the buyer echoes, not what you serve
&lt;/h2&gt;

&lt;p&gt;Bazaar-style catalogs (PayAI's, and per the x402 spec, any facilitator's) don't crawl your 402 response. They read the discovery declaration &lt;strong&gt;from the payment payload&lt;/strong&gt; that arrives at &lt;code&gt;/verify&lt;/code&gt; or &lt;code&gt;/settle&lt;/code&gt;. In x402 v2 the buyer's client is supposed to copy &lt;code&gt;extensions&lt;/code&gt; (and &lt;code&gt;resource&lt;/code&gt;) from your 402 into that payload.&lt;/p&gt;

&lt;p&gt;Many buyer clients don't. So you can have plenty of real settlements and still never be catalogued. Our testnet endpoints were listed only because &lt;em&gt;our own&lt;/em&gt; e2e test client used the official &lt;code&gt;@x402/fetch&lt;/code&gt; wrapper, which does echo the extension.&lt;/p&gt;

&lt;p&gt;PayAI's docs say this outright: a successful settlement alone does not list a resource; the payload has to carry the bazaar extension. Their &lt;code&gt;/verify&lt;/code&gt; and &lt;code&gt;/settle&lt;/code&gt; now return an &lt;code&gt;EXTENSION-RESPONSES&lt;/code&gt; header so you can tell what happened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;header&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{"bazaar":{"status":"processing"}}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;received, queued for cataloging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{"bazaar":{"status":"rejected",...}}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;received, failed validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no header&lt;/td&gt;
&lt;td&gt;the payload had no bazaar extension&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The fix (no money moves)
&lt;/h2&gt;

&lt;p&gt;You only need &lt;strong&gt;one&lt;/strong&gt; payload with the extension to reach the facilitator, and &lt;code&gt;/verify&lt;/code&gt; is enough. For a GET resource, the catalog admits it when its own read-only probe of your URL gets a 402 back.&lt;/p&gt;

&lt;p&gt;So: sign a payment from &lt;strong&gt;your own receive wallet to itself&lt;/strong&gt; (net zero even if it were ever settled), attach the bazaar extension from your live 402, and send it to &lt;code&gt;/verify&lt;/code&gt; only, never &lt;code&gt;/settle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;privateKeyToAccount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viem/accounts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x402Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@x402/core/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExactEvmScheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@x402/evm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;privateKeyToAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAY_TO_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// your receive wallet&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;x402Client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eip155:8453&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExactEvmScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acct&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                &lt;span class="c1"&gt;// unpaid -&amp;gt; 402&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment-required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPaymentPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bazaar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bazaar&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;   &lt;span class="c1"&gt;// make sure it's echoed&lt;/span&gt;
  &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://facilitator.payai.network/verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x402Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;paymentPayload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;paymentRequirements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;extension-responses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wallet needs a USDC balance at least the price for &lt;code&gt;/verify&lt;/code&gt; to report &lt;code&gt;isValid: true&lt;/code&gt;. Nothing is transferred, because we never call &lt;code&gt;/settle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We ran it over our 40 mainnet GET endpoints. Every one came back &lt;code&gt;{"bazaar":{"status":"processing"}}&lt;/code&gt;, and about a minute later the catalog query above returned &lt;code&gt;total: 40&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things worth checking on your own service
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Query &lt;code&gt;/discovery/resources?payTo=&amp;lt;your address&amp;gt;&lt;/code&gt; on every facilitator you settle through. Don't assume settlements mean you're listed.&lt;/li&gt;
&lt;li&gt;Look for the &lt;code&gt;EXTENSION-RESPONSES&lt;/code&gt; header in your own test runs. If it's missing, your client isn't echoing extensions.&lt;/li&gt;
&lt;li&gt;Each facilitator has its own catalog. Being in PayAI's says nothing about Coinbase CDP's Bazaar, which only sees payloads settled through CDP.&lt;/li&gt;
&lt;li&gt;Re-run the verify step when you add endpoints. New routes won't show up until some payload carrying their declaration reaches the facilitator.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A note on who's buying
&lt;/h2&gt;

&lt;p&gt;When we looked at the two wallets that paid us (wallet activity is public on Basescan), both also paid several other x402 sellers in the same minutes, mostly through the Coinbase facilitator. One wallet had funded the other directly, so it's most likely a single operator running a research agent across many paid APIs. The lesson for us: buyers shop across catalogs, so being missing from one catalog is lost sales, not a cosmetic gap.&lt;/p&gt;

&lt;p&gt;If you sell x402 APIs and have hit other discovery gotchas, I'd like to hear them in the comments.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What AI agents actually paid for in our first day of selling x402 APIs ($0.175, 18 calls, 2 wallets)</title>
      <dc:creator>ScrapeMole</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:46:48 +0000</pubDate>
      <link>https://dev.to/scrapemole/what-ai-agents-actually-paid-for-in-our-first-day-of-selling-x402-apis-0175-18-calls-2-wallets-28ca</link>
      <guid>https://dev.to/scrapemole/what-ai-agents-actually-paid-for-in-our-first-day-of-selling-x402-apis-0175-18-calls-2-wallets-28ca</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm the maker of ScrapeMole, the API this post is about. Every number below comes from our own Cloudflare logs and the public Base blockchain.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yesterday we put a set of pay-per-call APIs online for AI agents. There are no accounts and no API keys: an agent calls an endpoint, gets &lt;strong&gt;HTTP 402 Payment Required&lt;/strong&gt; with a price, pays with a signed USDC authorization over &lt;a href="https://www.x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt;, and gets the data. (The build itself is in the &lt;a href="https://dev.to/scrapemole/how-we-built-a-pay-per-call-x402-api-on-the-cloudflare-workers-free-plan-and-only-charge-on-4e5j"&gt;previous post&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;About 20 hours after launch, here's what agents actually paid for, what they only looked at, and what we changed because of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers so far
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Revenue: $0.175 USDC&lt;/strong&gt;, settled on Base to our receive address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;18 paid calls from 2 different wallets.&lt;/strong&gt; Neither wallet is ours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What they bought:&lt;/strong&gt; YouTube search 16 times, YouTube channel lookup once, a YouTube transcript once. Every paid call was a $0.005 or $0.01 endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What nobody has paid for yet:&lt;/strong&gt; the 25 endpoints we added last night (web reader, DNS/WHOIS, onchain prices, SEC company data, Wikipedia). They're less than a day old.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$0.175 isn't a business. But it shows the loop working: real agents found the API through directories with no human in between, paid, and came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: being listed early mattered more than the product itself
&lt;/h2&gt;

&lt;p&gt;The paid calls all went to the origin that had been listed the longest: our first worker, registered on &lt;a href="https://www.x402scan.com" rel="noopener noreferrer"&gt;x402scan&lt;/a&gt;, x402dash, Agent402 and &lt;a href="https://402index.io" rel="noopener noreferrer"&gt;402 Index&lt;/a&gt; about 8 hours before the others. Wikipedia lookups or DNS records aren't less useful than YouTube search. Agents just hadn't seen them yet.&lt;/p&gt;

&lt;p&gt;So the first rule of selling to agents seems to be the same as selling to people: be where they look, and be there early. We now list every new endpoint on four x402 directories and the official MCP Registry within the hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: one agent doing a task is worth more than many browsing
&lt;/h2&gt;

&lt;p&gt;One wallet made 15 calls in 13 minutes, all to the same search endpoint with different queries. That looks like an agent working through a research task in a loop, not someone kicking the tires. A tool that's cheap, fast and predictable inside a loop gets reused.&lt;/p&gt;

&lt;p&gt;That's why we keep prices at $0.001 to $0.01 and &lt;strong&gt;settle only when the response is a 200&lt;/strong&gt;. If the upstream fails, times out or the input is bad, the agent pays nothing. An agent (or the person who set its budget) can't easily tell a bad result from a bad tool, so we take that risk instead of the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: discovery traffic dwarfs paid traffic
&lt;/h2&gt;

&lt;p&gt;Across our seven origins, Cloudflare's logs show about 6,100 requests since launch against 18 paid calls, so roughly 340 free requests for every paid one. Most of it is automated discovery and monitoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;roughly &lt;strong&gt;10 to 12 unpaid 402 probes per endpoint&lt;/strong&gt; on the new origins in their first night. That's directories and crawlers checking the price and payment details;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;about 2,800 POSTs to the remote MCP endpoints&lt;/strong&gt; (&lt;code&gt;/mcp&lt;/code&gt;), 300+ per origin, from MCP indexes and clients connecting and listing tools. None of them has turned into a paid tool call yet;&lt;/li&gt;
&lt;li&gt;about &lt;strong&gt;740 fetches of &lt;code&gt;openapi.json&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;200 of &lt;code&gt;llms.txt&lt;/code&gt;&lt;/strong&gt;, plus a steady trickle of POST/PUT/DELETE guesses at the paid routes. Agents and scanners probing what the API accepts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The takeaway: your machine-readable surfaces (OpenAPI, the 402 body, MCP tool descriptions) are your storefront. Write them for a model that decides in one pass whether your tool fits its task. Say &lt;em&gt;when&lt;/em&gt; to use the tool, not only what it returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped because of this: ScrapeMole Pulse
&lt;/h2&gt;

&lt;p&gt;Language models are frozen in time. They don't know today's date in Tokyo, this morning's EUR/USD rate, or whether it'll rain in Vilnius tomorrow. Those are small, boring, constant needs, which makes them a good fit for pay-per-call. So today we launched &lt;strong&gt;&lt;a href="https://pulse.scrapemole.workers.dev" rel="noopener noreferrer"&gt;ScrapeMole Pulse&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/fx/latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ECB reference rates for ~30 currencies, any base&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/fx/convert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Convert an amount at the latest or a historical ECB rate (since 1999)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/fx/history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Daily rate series up to a year, with min/max/change&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/time/now&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current local time, offset and DST in up to 20 time zones&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/time/convert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DST-aware conversion for scheduling ("15:00 New York" in Vilnius and Tokyo)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/weather&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MET Norway forecast by coordinates or place name: now, hourly, 9 days&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/news/hn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hacker News top/new/best/ask/show lists with points and comments&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every response names its source and licence: ECB data via the open-source Frankfurter API, weather from MET Norway (CC BY 4.0), place names resolved through Wikidata (CC0), and the official Hacker News API (we leave out usernames). Time-zone math runs on the IANA tz database built into the runtime, so it has no upstream to fail.&lt;/p&gt;

&lt;p&gt;Before going live, Pulse passed a paid end-to-end test on Base Sepolia: 19 of 19 checks, $0.010 spent by the test buyer and $0.010 received, and $0 charged for the three calls designed to fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full lineup
&lt;/h2&gt;

&lt;p&gt;All on the Cloudflare Workers free plan, all with &lt;code&gt;openapi.json&lt;/code&gt;, &lt;code&gt;llms.txt&lt;/code&gt; and a remote MCP server at &lt;code&gt;/mcp&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YouTube + Google Trends&lt;/strong&gt;: search, channels, transcripts, interest over time — &lt;a href="https://scrapemole-api-mainnet.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://scrapemole-api-mainnet.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reader&lt;/strong&gt;: any page as Markdown, link previews, RSS feeds, links, sitemaps — &lt;a href="https://reader.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://reader.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domains&lt;/strong&gt;: DNS, RDAP/WHOIS, availability, email security, SSL, subdomains — &lt;a href="https://domains.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://domains.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onchain&lt;/strong&gt;: Base/Ethereum prices, DEX prices, ENS/Basenames, gas, balances, transactions — &lt;a href="https://onchain.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://onchain.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companies&lt;/strong&gt;: SEC EDGAR profiles, XBRL financials, filings — &lt;a href="https://companies.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://companies.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wiki&lt;/strong&gt;: Wikipedia summaries and search, Wikidata facts (attributed) — &lt;a href="https://wiki.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://wiki.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulse&lt;/strong&gt;: FX, time zones, weather, Hacker News — &lt;a href="https://pulse.scrapemole.workers.dev" rel="noopener noreferrer"&gt;https://pulse.scrapemole.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try one from the terminal without paying. You'll get the 402 with the exact price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://pulse.scrapemole.workers.dev/v1/fx/convert?from=USD&amp;amp;to=EUR&amp;amp;amount=250"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then retry with any x402 client (for example &lt;code&gt;@x402/fetch&lt;/code&gt;) to pay and get the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Our 30-day goal is modest and public: &lt;strong&gt;at least 3 different paying wallets and $100 in total.&lt;/strong&gt; We're at 2 wallets and $0.175. Next up are more listings for the new origins and Solana USDC as a second payment network (code is done and tested on devnet). We'll keep adding small tools in the categories agents actually pay for.&lt;/p&gt;

&lt;p&gt;If you're building agents and want a data source we don't have, say so in the comments. If it's public, non-personal data and we can serve it reliably, we'll build it.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>ai</category>
      <category>api</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>How we built a pay-per-call x402 API on the Cloudflare Workers free plan (and only charge on success)</title>
      <dc:creator>ScrapeMole</dc:creator>
      <pubDate>Sun, 11 Oct 2026 18:51:23 +0000</pubDate>
      <link>https://dev.to/scrapemole/how-we-built-a-pay-per-call-x402-api-on-the-cloudflare-workers-free-plan-and-only-charge-on-4e5j</link>
      <guid>https://dev.to/scrapemole/how-we-built-a-pay-per-call-x402-api-on-the-cloudflare-workers-free-plan-and-only-charge-on-4e5j</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm the maker of ScrapeMole, the API this post is about. All the numbers below come from our own test reports. There are no paying users yet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI agents are good at finding tools and bad at signing up for them. An agent can read an OpenAPI spec in milliseconds, but it can't open an account, confirm an email and paste an API key without a human. &lt;a href="https://www.x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt; skips that step: the server answers &lt;strong&gt;HTTP 402 Payment Required&lt;/strong&gt; with a price, the client pays with a signed stablecoin authorization in a header, and gets the response.&lt;/p&gt;

&lt;p&gt;ScrapeMole is a test of that idea: a few public web data endpoints (YouTube search, channel videos, transcripts, Google Trends, Amazon product pages) at $0.005 to $0.01 per call, paid in USDC on Base. This post covers how it works, how it runs on Cloudflare's free plan, and the problems we hit along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payment flow
&lt;/h2&gt;

&lt;p&gt;Call any paid endpoint without paying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://scrapemole-api-mainnet.scrapemole.workers.dev/v1/youtube/search?q=espresso"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 402
payment-required: eyJ4NDAyVmVyc2lvbiI6Mi...   (base64 of the JSON below)

{
  "x402Version": 2,
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "10000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0xCb535d13C7fC5Fe6C2B88da3f3601B840CCaf8Bc",
    "maxTimeoutSeconds": 300
  }]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Trimmed: the real response also has a resource description and a Bazaar discovery extension with the input schema and an example output.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;amount&lt;/code&gt; is in USDC base units (6 decimals), so &lt;code&gt;10000&lt;/code&gt; is $0.01. The client signs an EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt; for exactly that amount and retries with a &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; header. With &lt;code&gt;@x402/fetch&lt;/code&gt; it's a wrapper around &lt;code&gt;fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;wrapFetchWithPaymentFromConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@x402/fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExactEvmScheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@x402/evm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;privateKeyToAccount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viem/accounts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;privateKeyToAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AGENT_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// a wallet with a little USDC on Base&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapFetchWithPaymentFromConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;schemes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eip155:8453&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExactEvmScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://scrapemole-api-mainnet.scrapemole.workers.dev/v1/youtube/search?q=espresso&amp;amp;limit=10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buyer pays no gas: the facilitator submits the authorization on-chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Failed calls are free" has to be enforced, not promised
&lt;/h2&gt;

&lt;p&gt;This was the property we cared about most. A scraping API fails for reasons the caller can't control: the video has no captions, the channel doesn't exist, the source rate-limits you. Charging for those calls would make agents avoid us.&lt;/p&gt;

&lt;p&gt;The x402 flow makes this simple to enforce. The server sends the payment to the facilitator (we use PayAI) for &lt;strong&gt;verification&lt;/strong&gt; before doing any work, and requests &lt;strong&gt;settlement&lt;/strong&gt; only after the handler has returned a 200. Every other status is a JSON error with &lt;code&gt;"charged": false&lt;/code&gt;, and the authorization is never settled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INVALID_INPUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing or malformed parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NOT_FOUND&lt;/code&gt; / &lt;code&gt;NO_DATA&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Target doesn't exist, or has nothing to return&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCKED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source rate-limited us even after retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;502/504&lt;/td&gt;
&lt;td&gt;upstream error / timeout&lt;/td&gt;
&lt;td&gt;Try again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We test this end to end on Base Sepolia with throwaway wallets, comparing on-chain USDC balances before and after the run. The latest run passed &lt;strong&gt;13/13 checks&lt;/strong&gt;: four unpaid calls got correct 402s, four paid calls returned data, four deliberately failing paid calls (unknown video, unknown channel, missing query, impossible time range) returned errors, and the route we had disabled was confirmed to be absent. The buyer's balance went down by exactly &lt;strong&gt;$0.035&lt;/strong&gt;, the sum of the four successful calls. The failing calls cost &lt;strong&gt;$0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you build something similar, put the balance check in your test. It's the only way to be sure that "not charged" really holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running on the Cloudflare Workers free plan
&lt;/h2&gt;

&lt;p&gt;The free plan gives you roughly 10 ms of CPU per request (network wait time doesn't count) and 100k requests a day. For endpoints that call a JSON API and reshape the result, like YouTube's internal API and Google Trends, 10 ms was enough. Three things needed work.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Egress regions get bot-checked differently
&lt;/h3&gt;

&lt;p&gt;Datacenter IPs get bot-checked, and Cloudflare's are no exception. What surprised us was how much it depended on where the Worker ran. On our first live probe from Workers, YouTube transcripts succeeded &lt;strong&gt;1 time in 5&lt;/strong&gt; and Google Trends &lt;strong&gt;0 in 5&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What fixed it: when a run comes back &lt;code&gt;BLOCKED&lt;/code&gt;, re-run it inside a &lt;strong&gt;Durable Object created with a &lt;code&gt;locationHint&lt;/code&gt;&lt;/strong&gt;, which places it, and its outbound requests, in another region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eeur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SCRAPER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SCRAPER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;idFromName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`relay-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locationHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://relay/run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our tests, Western Europe, Eastern Europe and South America passed where the other locations were blocked. With the retry, the next probes passed &lt;strong&gt;10/10 for transcripts&lt;/strong&gt; and &lt;strong&gt;10/10 for Google Trends&lt;/strong&gt;. YouTube search and channel listings never needed it (5/5 each). It uses no proxies, and since the retry only runs when the first try was blocked, it costs nothing in the normal case.&lt;/p&gt;

&lt;p&gt;This is not a guarantee: the blocking is the source's decision and can change at any time. That's one more reason a blocked call must never be charged.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Some parsing simply doesn't fit in 10 ms
&lt;/h3&gt;

&lt;p&gt;Parsing a full Amazon product page with cheerio went over the CPU limit. Rather than pay for a plan, that one endpoint runs on a Vercel function. The Worker links to it from its &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;llms.txt&lt;/code&gt;, so agents still find it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Don't share in-flight I/O across requests
&lt;/h3&gt;

&lt;p&gt;The x402 server middleware syncs with the facilitator (&lt;code&gt;GET /supported&lt;/code&gt;) when it initializes. If that happens during one request and a second request waits on the same promise, workerd refuses: a Worker can't await I/O that another request started. Our fix: build everything that doesn't need I/O (routes, discovery schemas) at startup, initialize inside the first paid request, and cache it only after it succeeds. If the facilitator is unreachable, the call returns a 503 (not charged) and the next request tries again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovery: there's no app store
&lt;/h2&gt;

&lt;p&gt;Agents only find you if you publish machine-readable metadata. We serve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openapi.json&lt;/code&gt;, with an &lt;code&gt;x-payment-info&lt;/code&gt; block (price, network, asset) on every paid operation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/.well-known/x402&lt;/code&gt;, plus the Bazaar discovery extension inside every 402 (input schema and an example output)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llms.txt&lt;/code&gt;, a plain-text guide for LLMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One compatibility note: x402 v2 puts the payment challenge only in the &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header, and some indexers built for v1 still read &lt;code&gt;accepts&lt;/code&gt; from the body. We copy the decoded challenge into the body as well, and one indexer that had rejected us accepted the listing after that.&lt;/p&gt;

&lt;p&gt;Several x402 directories (x402scan, x402dash, Agent402, 402 Index) accepted our listing with one API call each, with no account.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same tools over MCP
&lt;/h2&gt;

&lt;p&gt;There's also a remote MCP server at &lt;a href="https://scrapemole-api-mainnet.scrapemole.workers.dev/mcp" rel="noopener noreferrer"&gt;&lt;code&gt;/mcp&lt;/code&gt;&lt;/a&gt; (Streamable HTTP, stateless) using the &lt;a href="https://github.com/coinbase/x402/blob/main/specs/transports-v2/mcp.md" rel="noopener noreferrer"&gt;x402 MCP transport&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://scrapemole-api-mainnet.scrapemole.workers.dev/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tools/list&lt;/code&gt; is free. An unpaid &lt;code&gt;tools/call&lt;/code&gt; returns &lt;code&gt;isError: true&lt;/code&gt; with the same PaymentRequired object in &lt;code&gt;structuredContent&lt;/code&gt;. The client retries with the signed payment in &lt;code&gt;params._meta["x402/payment"]&lt;/code&gt;, and the settlement receipt comes back in &lt;code&gt;result._meta["x402/payment-response"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Internally, each MCP tool just calls the matching HTTP route with a &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; header, so there's one verify/settle code path for both interfaces. The MCP version of the testnet test passed &lt;strong&gt;9/9&lt;/strong&gt;: one paid search settled at exactly $0.01, and a paid call with invalid input returned &lt;code&gt;INVALID_INPUT&lt;/code&gt; without settling. The server is in the official MCP Registry as &lt;code&gt;dev.pages.scrapemole/scrapemole&lt;/code&gt;; the registry accepted proof of domain ownership through a file on our site, with no GitHub account needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to run
&lt;/h2&gt;

&lt;p&gt;Nothing so far. Workers, Durable Objects, cron monitoring and the website are on Cloudflare's free tiers, and Amazon runs on a Vercel free function. PayAI's facilitator also has a free allowance for settlements, and the buyer pays no gas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open questions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Will agent builders actually pay per call?&lt;/strong&gt; It's live on Base mainnet, but no one has paid yet. Prepaid credits with an API key might still be easier for teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long will the regional retry work?&lt;/strong&gt; I expect to keep measuring it with the cron canary that runs every 30 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What should come next?&lt;/strong&gt; If there's a source you'd want behind a 402, I'd like to hear it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docs, prices and live test numbers: &lt;a href="https://scrapemole.pages.dev" rel="noopener noreferrer"&gt;scrapemole.pages.dev&lt;/a&gt;. The API's &lt;code&gt;openapi.json&lt;/code&gt; and &lt;code&gt;llms.txt&lt;/code&gt; are linked there.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
