<?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: tigerops-win</title>
    <description>The latest articles on DEV Community by tigerops-win (@tigeropswin).</description>
    <link>https://dev.to/tigeropswin</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%2F4156110%2F0f3c6e9e-9bc7-47b1-8f2a-3f9063a0cec4.png</url>
      <title>DEV Community: tigerops-win</title>
      <link>https://dev.to/tigeropswin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tigeropswin"/>
    <language>en</language>
    <item>
      <title>How I put a data product behind x402 so AI agents can buy it with USDC</title>
      <dc:creator>tigerops-win</dc:creator>
      <pubDate>Fri, 02 Oct 2026 02:28:52 +0000</pubDate>
      <link>https://dev.to/tigeropswin/how-i-put-a-data-product-behind-x402-so-ai-agents-can-buy-it-with-usdc-3e75</link>
      <guid>https://dev.to/tigeropswin/how-i-put-a-data-product-behind-x402-so-ai-agents-can-buy-it-with-usdc-3e75</guid>
      <description>&lt;p&gt;I sell verified B2B lead packs. For a one-off campaign, a $49/month data seat is a terrible deal — and for an AI agent, a subscription signup is a non-starter. Agents can't fill out billing forms. So I put the product behind x402: HTTP 402 "Payment Required", resurrected as a machine checkout. Here's exactly how it works, with the real shapes and flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What x402 actually is
&lt;/h2&gt;

&lt;p&gt;x402 (from Coinbase) turns the 402 status code into a payment handshake an agent can complete without human help:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent requests a paid resource with no payment attached.&lt;/li&gt;
&lt;li&gt;The server responds &lt;code&gt;402&lt;/code&gt; with machine-readable payment terms in the body.&lt;/li&gt;
&lt;li&gt;The agent pays (USDC on a supported network) and retries the request with an &lt;code&gt;X-Payment&lt;/code&gt; header carrying proof of payment.&lt;/li&gt;
&lt;li&gt;The server verifies the payment on-chain and serves the resource.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No accounts, no API keys, no OAuth dance for the buyer — the wallet is the identity, and the retry loop is the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of a 402 response
&lt;/h2&gt;

&lt;p&gt;The 402 body isn't an error page. It's an offer. Here's the actual structure I return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x402Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0xYourTreasuryWallet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxAmountRequired"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxTimeoutSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://scoutpacks-tunnel-1.loca.lt/buy/pack-25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25 verified B2B leads, JSON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;payTo&lt;/code&gt; — your treasury wallet. The agent pays this address directly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxAmountRequired&lt;/code&gt; — in the asset's base units. USDC has 6 decimals, so &lt;code&gt;9000000&lt;/code&gt; = $9.00. This is the single most common bug I see: people write dollars and charge micro-cents.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;asset&lt;/code&gt; — the token contract (above is USDC on Base).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;network&lt;/code&gt; — &lt;code&gt;base&lt;/code&gt; here. Match your payTo wallet to a chain you actually monitor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scheme&lt;/code&gt; — &lt;code&gt;exact&lt;/code&gt; means exact-amount settlement.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource&lt;/code&gt; — which endpoint the terms apply to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also serve a machine-readable manifest at &lt;code&gt;/.well-known/x402&lt;/code&gt; so agents can discover the catalog and terms &lt;em&gt;before&lt;/em&gt; touching a paid endpoint — cheaper than learning via 402s.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pay-and-retry flow
&lt;/h2&gt;

&lt;p&gt;Client side (the agent's wallet code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://scoutpacks-tunnel-1.loca.lt/buy/pack-25&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepts&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# Build + sign the USDC transfer per the scheme, then:
&lt;/span&gt;    &lt;span class="n"&gt;payment_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sign_transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# base64, per the x402 spec
&lt;/span&gt;    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Payment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payment_payload&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;leads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="c1"&gt;# 200 — the goods
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server side (FastAPI-style):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/buy/{pack_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pack_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;pack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CATALOG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pack_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x_payment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;payment_terms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;verify_onchain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# re-check amount, asset, recipient, settlement
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;# JSON leads: instant for the 25-pack
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth internalizing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Re-verify on every retry.&lt;/strong&gt; The &lt;code&gt;X-Payment&lt;/code&gt; header is untrusted input. Check recipient, amount, asset, and that the transaction actually settled — or use a facilitator service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts are terms, not suggestions.&lt;/strong&gt; &lt;code&gt;maxTimeoutSeconds&lt;/code&gt; bounds how long the quote is valid; re-quote after it lapses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the catalog single-sourced.&lt;/strong&gt; The 402 handler, the &lt;code&gt;/.well-known/x402&lt;/code&gt; manifest, and delivery should all read the same catalog object. If price lives in three places, it will drift.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The MCP wrapper: let agents browse before they buy
&lt;/h2&gt;

&lt;p&gt;Raw HTTP is fine, but agents live in tool-calling land. The repo ships an MCP server with two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_packs()&lt;/code&gt; — returns the catalog: pack sizes, prices, and exactly what each pack contains. No payment needed; this is the menu.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buy_pack(pack_id)&lt;/code&gt; — runs the pay-and-retry flow above and returns the leads JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MCP server reads the same catalog the 402 endpoint uses, so the menu and the checkout can never disagree. The pattern generalizes: &lt;code&gt;list_*&lt;/code&gt; (free, discoverable) + &lt;code&gt;buy_*&lt;/code&gt; (402-gated) is a good shape for any agent-sold data product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The working example: Scout Packs
&lt;/h2&gt;

&lt;p&gt;Scout Packs is the live implementation of everything above: verified B2B lead packs for AI agents — 25 leads for $9, 50 for $15, 100 for $25. Every lead ships as JSON: company, contact, title, published email, plus the source URL it was verified against.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live endpoint: &lt;a href="https://scoutpacks-tunnel-1.loca.lt" rel="noopener noreferrer"&gt;https://scoutpacks-tunnel-1.loca.lt&lt;/a&gt; (the 25-pack delivers instantly; 50/100 within 24h)&lt;/li&gt;
&lt;li&gt;Machine-readable manifest: &lt;a href="https://scoutpacks-tunnel-1.loca.lt/.well-known/x402" rel="noopener noreferrer"&gt;https://scoutpacks-tunnel-1.loca.lt/.well-known/x402&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo with the x402 handler and the MCP server (&lt;code&gt;list_packs&lt;/code&gt; / &lt;code&gt;buy_pack&lt;/code&gt;): &lt;a href="https://github.com/tigerops-win/scout-packs" rel="noopener noreferrer"&gt;https://github.com/tigerops-win/scout-packs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're selling anything to agents — data, compute, API access — stop minting API keys and start returning 402s. The wallet is the account.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>ai</category>
      <category>web3</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
