<?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: Max Holloway</title>
    <description>The latest articles on DEV Community by Max Holloway (@max_holloway).</description>
    <link>https://dev.to/max_holloway</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3718173%2F9a06784c-857f-4b01-a3a9-c441f879647c.jpg</url>
      <title>DEV Community: Max Holloway</title>
      <link>https://dev.to/max_holloway</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/max_holloway"/>
    <language>en</language>
    <item>
      <title>What I Learned Building a 402-Powered API for Agent Workflows</title>
      <dc:creator>Max Holloway</dc:creator>
      <pubDate>Wed, 20 May 2026 19:35:37 +0000</pubDate>
      <link>https://dev.to/max_holloway/what-i-learned-building-a-402-powered-api-for-agent-workflows-5kj</link>
      <guid>https://dev.to/max_holloway/what-i-learned-building-a-402-powered-api-for-agent-workflows-5kj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I used AI as an editing/drafting assistant for this post, but the architecture, examples, and technical claims here are mine and were reviewed before publishing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most paid APIs still assume a familiar model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issue an API key&lt;/li&gt;
&lt;li&gt;put the user on a plan&lt;/li&gt;
&lt;li&gt;count usage in the background&lt;/li&gt;
&lt;li&gt;send an invoice later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That works fine for many developer products, but I kept running into a mismatch when thinking about agent workflows.&lt;/p&gt;

&lt;p&gt;While building &lt;a href="https://mintapi.dev" rel="noopener noreferrer"&gt;MintAPI&lt;/a&gt;, I kept coming back to the same question: are API keys and subscriptions really the right default for software that makes bursty, task-driven requests on behalf of an agent?&lt;/p&gt;

&lt;p&gt;That led me to experiment with a paid API flow built around &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402" rel="noopener noreferrer"&gt;&lt;code&gt;402 Payment Required&lt;/code&gt;&lt;/a&gt; / &lt;code&gt;x402&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;The request flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the client makes a normal HTTP request&lt;/li&gt;
&lt;li&gt;the server responds with a payment challenge&lt;/li&gt;
&lt;li&gt;the client signs the payment using its own signer infrastructure&lt;/li&gt;
&lt;li&gt;the client retries the request with payment attached&lt;/li&gt;
&lt;li&gt;the server verifies payment and serves the response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my case, this became a paid API surface for agent workflows, with a buyer-side SDK and a seller-side gateway.&lt;/p&gt;

&lt;p&gt;The interesting part was not just "can I charge for an API call?" The interesting part was whether the payment step could feel like a normal transport concern instead of a separate billing workflow glued on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I didn't want API keys to be the whole story
&lt;/h2&gt;

&lt;p&gt;I am not against API keys. They are simple, familiar, and easy to document.&lt;/p&gt;

&lt;p&gt;But for this project, they felt like the wrong abstraction for a few reasons:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. API keys identify access, not payment intent
&lt;/h3&gt;

&lt;p&gt;An API key says "this caller is allowed to use the API." It doesn't naturally express "this specific request should carry a payment."&lt;/p&gt;

&lt;p&gt;That usually pushes billing into metering systems, account plans, quotas, and reconciliation logic outside the request itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Agent traffic is uneven
&lt;/h3&gt;

&lt;p&gt;Human-oriented products often map well to seats, monthly plans, or generous quotas.&lt;/p&gt;

&lt;p&gt;Agent workloads often don't. You can have very low average usage with sharp spikes. In that world, a request-by-request payment model felt more honest.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I wanted the buyer to own signing
&lt;/h3&gt;

&lt;p&gt;One thing I wanted to avoid was hiding too much payment logic inside the API provider.&lt;/p&gt;

&lt;p&gt;The provider should verify payment. But the actual signing should happen in the buyer runtime, or in wallet infrastructure the buyer controls.&lt;/p&gt;

&lt;p&gt;That boundary ended up shaping the whole SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two sides of the system
&lt;/h2&gt;

&lt;p&gt;I ended up with two separate pieces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seller side: the gateway
&lt;/h3&gt;

&lt;p&gt;The seller-side gateway does a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validates the request&lt;/li&gt;
&lt;li&gt;decides whether the route requires payment&lt;/li&gt;
&lt;li&gt;returns a payment challenge when needed&lt;/li&gt;
&lt;li&gt;verifies and settles payment&lt;/li&gt;
&lt;li&gt;calls the upstream provider&lt;/li&gt;
&lt;li&gt;returns a normalized response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail I cared about a lot: &lt;strong&gt;validation happens before payment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means malformed requests should fail fast with a normal error instead of charging the caller for a bad request. If someone sends an invalid query, the system should return a &lt;code&gt;400&lt;/code&gt;, not trigger a paid flow first.&lt;/p&gt;

&lt;p&gt;That sounds obvious, but it is exactly the kind of edge case that makes paid APIs annoying if you get it wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buyer side: the SDK
&lt;/h3&gt;

&lt;p&gt;On the client side, I wanted the integration to feel close to ordinary HTTP.&lt;/p&gt;

&lt;p&gt;The SDK flow is roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make request&lt;/li&gt;
&lt;li&gt;detect &lt;code&gt;402&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;inspect supported payment routes&lt;/li&gt;
&lt;li&gt;resolve a signer&lt;/li&gt;
&lt;li&gt;sign&lt;/li&gt;
&lt;li&gt;retry with a payment header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also wanted the signer logic to stay out of endpoint code.&lt;/p&gt;

&lt;p&gt;Instead of every endpoint handler deciding how to sign, the SDK uses a signer resolution layer. In practice that means the client can route signing based on network or signer family rather than scattering that logic across all API calls.&lt;/p&gt;

&lt;p&gt;That made the buyer SDK much easier to reason about. If you want to see the public client surface, the &lt;a href="https://github.com/maks1302/mintapi-gateway" rel="noopener noreferrer"&gt;MintAPI gateway SDK&lt;/a&gt; shows the buyer-side flow more directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why signer resolution mattered more than I expected
&lt;/h2&gt;

&lt;p&gt;This was probably the most important design choice in the client.&lt;/p&gt;

&lt;p&gt;At first glance, "just pass a signer" sounds enough. In practice, it gets messy quickly.&lt;/p&gt;

&lt;p&gt;Different buyers may want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one default signer&lt;/li&gt;
&lt;li&gt;different signers per network&lt;/li&gt;
&lt;li&gt;different signers per signer family&lt;/li&gt;
&lt;li&gt;managed wallet infrastructure&lt;/li&gt;
&lt;li&gt;HSM/KMS-backed signing&lt;/li&gt;
&lt;li&gt;different preferences depending on environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that logic leaks into every API call, the client becomes brittle very quickly.&lt;/p&gt;

&lt;p&gt;So I treated signer resolution as its own concern. Endpoint code asks for a signer indirectly. A resolver decides how that happens.&lt;/p&gt;

&lt;p&gt;That separation made the client easier to extend and reduced a lot of hidden complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling is part of the product
&lt;/h2&gt;

&lt;p&gt;One thing I did not want was a payment-aware client that collapses every failure into "request failed."&lt;/p&gt;

&lt;p&gt;That makes it hard for agent runtimes to react correctly.&lt;/p&gt;

&lt;p&gt;There is a real difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the server issued a payment challenge&lt;/li&gt;
&lt;li&gt;the client could not find a supported payment network&lt;/li&gt;
&lt;li&gt;the signer was unavailable&lt;/li&gt;
&lt;li&gt;payment header creation failed&lt;/li&gt;
&lt;li&gt;the upstream API failed after payment succeeded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are operationally different failures. So the client surface exposes typed errors instead of one generic failure mode.&lt;/p&gt;

&lt;p&gt;That sounds like a small SDK detail, but for automation it matters a lot. An agent can retry one class of error, escalate another, and treat a third as a real product failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route discovery turned out to be useful
&lt;/h2&gt;

&lt;p&gt;I also added a machine-readable route discovery endpoint.&lt;/p&gt;

&lt;p&gt;Originally this was mostly for internal convenience. I wanted one registry for routes, pricing, and validation metadata.&lt;/p&gt;

&lt;p&gt;But it became useful for other reasons too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docs generation&lt;/li&gt;
&lt;li&gt;SDK helpers&lt;/li&gt;
&lt;li&gt;machine-readable discovery for tools and agents&lt;/li&gt;
&lt;li&gt;consistency between the gateway and the docs surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building APIs for software instead of humans alone, discoverability becomes much more important.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still feels unresolved
&lt;/h2&gt;

&lt;p&gt;I like this model more than I expected, but I do not think it answers everything.&lt;/p&gt;

&lt;p&gt;A few open questions still feel real:&lt;/p&gt;

&lt;h3&gt;
  
  
  Is &lt;code&gt;402&lt;/code&gt; intuitive enough?
&lt;/h3&gt;

&lt;p&gt;I think it is elegant at the protocol level. I am less certain that it is immediately intuitive for most developers the first time they see it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is pay-per-request always the right fit?
&lt;/h3&gt;

&lt;p&gt;Probably not. Some teams will still prefer API keys, quotas, and predictable monthly spend. That tradeoff does not disappear.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much payment logic should the client know about?
&lt;/h3&gt;

&lt;p&gt;I still think buyer-side signing is the right boundary, but there is a balancing act between giving the client enough control and making adoption too complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  My current takeaway
&lt;/h2&gt;

&lt;p&gt;The main thing I learned is that &lt;strong&gt;payment can be modeled as part of the request lifecycle&lt;/strong&gt;, not just as an account-level concern.&lt;/p&gt;

&lt;p&gt;That does not make subscriptions or API keys obsolete. But for agent-oriented workloads, I think there is real value in a model where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each request can carry payment intent&lt;/li&gt;
&lt;li&gt;the buyer controls signing&lt;/li&gt;
&lt;li&gt;the gateway verifies before serving&lt;/li&gt;
&lt;li&gt;the client can treat payment as a first-class transport step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That ended up feeling more natural for software-to-software usage than I expected.&lt;/p&gt;

&lt;p&gt;If you want to see the product that came out of this experiment, &lt;a href="https://mintapi.dev" rel="noopener noreferrer"&gt;MintAPI&lt;/a&gt; is the live implementation I used as the test bed for these ideas.&lt;/p&gt;

&lt;p&gt;If you've worked on paid APIs, agent infrastructure, or protocol-level billing, I would be interested in your take: would you rather integrate a &lt;code&gt;402&lt;/code&gt; flow like this, or stick with the usual API key + usage-plan model?&lt;/p&gt;

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