AI Agents Can Pay APIs Now. I Ran the Same Request Through Two Rails
The short verdict
- PayKit turns an HTTP
402 Payment Requiredinto a payment challenge that an agent can sign and replay. - I sent the same
GET /api/v1/fortunethrough both MPP charge and x402 exact. Both paths ended in a normal 200 response. - MPP also carried a split for
/api/v1/joke: $0.003 to a platform account and the rest to the seller, with a receipt that kept the seller payout label. - Use it when your API needs small stablecoin charges and the payer can control a wallet. Do not treat it as a card network. There are no chargebacks, and the package has a rough edge that I hit before the demo ran.
The first response was 402
I cloned Solana Foundation's PayKit repository, started its Playground API, and called GET /api/v1/fortune with an ordinary HTTP client. The server answered with 402 Payment Required.
That was the useful part, not an error to hide. The response said the request cost 10,000 USDC base units, or $0.01, and advertised two offers in the same accepts array. One offer was x402 with the exact scheme. The other was MPP with the charge intent.
Then I gave the PayKit client a throwaway Solana keypair and replayed the same URL with MPP selected. The client read the challenge, signed the charge, sent the proof, and got a JSON response:
One honesty detail matters here. I did not hand an LLM a prompt and claim that it chose a payment. I ran PayKit's createPayKitClient as the machine-side HTTP client an AI agent could call. Reading the 402, signing the requested payment, and replaying the same URL are the agent-facing payment steps I was testing.
{"fortune":"Your code will compile on the first try today."}
The response included a payment-receipt and an x-payment-settlement-signature. I queried the hosted sandbox RPC for that reference. The transaction existed, and its err field was null. That is the line between “the HTTP demo returned 200” and “the payment path actually settled.”
What the SDK keeps out of your route handler
The application surface is small. The TypeScript example passes accepted protocols, an operator, and a pricing table to createPayKit, then attaches pay.express('fortune') to an Express route.
An unpaid request gets a 402. A request with a payment proof gets verified and settled before the handler runs. The handler can read the verified receipt with pay.payment(req). It does not need an x402 branch beside an MPP branch for every paid route.
The repository's interface specification makes the boundary explicit. The application declares a priced gate. A dispatcher collects protocol offers and detects the credential. An adapter verifies and settles the payment. The application sees a protocol-neutral Payment object.
I checked that claim against the repository rather than treating it as a slogan. docs/paykit-interface.md names the three layers and the require_payment, paid?, and payment() application primitives. TypeScript's src/client/index.ts contains the 402 probe, signing path, and retry. The Playground API's index.ts wires the price table to the fortune, joke, and summarize routes.
That separation matters for an agent. The agent needs to understand a payment challenge, not a merchant's private routing table. The server can change its accepted rail without changing the business handler.
Two rails behind one challenge
x402 exact
x402 is the compact path. The server sends a recipient, amount, network, and scheme. The client signs a USDC transfer, attaches the payment proof to the original HTTP request, and retries it. The server verifies the settlement and returns the resource.
I called the same fortune route with x402 selected. The client progress went through challenge, signing, paying, and paid. The server returned an x-payment-response containing a sandbox transaction signature. I looked that signature up through RPC: slot 436078484, with no transaction error.
This is a good fit for one endpoint with one price and one recipient. It does not make the payment reversible. In my run, the client stayed inside the HTTP request, signed the payment proof, and received 200 without opening a card checkout. That does not replace the operations a card product provides.
MPP charge
MPP carries a richer payment intent. The difference showed up on the joke route. Its price was $0.01, with $0.003 taken inside the charge for the platform and the remainder paid to the seller.
The MPP challenge contained the split recipients and memo fields. After I called the route with the same PayKit client, the server returned the joke and a receipt containing the seller payout label. I reread the settlement reference through the sandbox RPC. It existed with err: null.
This is not just a longer version of x402. A marketplace needs to say who gets paid, how much, and which internal sale the payout belongs to. MPP puts those details into the challenge and the receipt while keeping the application route at the same 402 boundary.
Why one route becomes MPP-only
The example makes joke MPP-only because the payment shape demands it. x402 exact centers on a single-recipient transfer. The split is not something PayKit pretends both protocols can express identically, so the gate narrows to MPP.
That is a healthy abstraction. accept: ['x402', 'mpp'] is not a promise that every route supports both rails. A fixed charge can offer both. A split charge can offer MPP. A usage gate can authorize an x402 ceiling and settle only the amount consumed. A subscription or session has another intent altogether.
The client chooses a compatible offer from the challenge. The server still owns the price, recipients, expiry, and accepted protocols. The agent is not negotiating a blank check.
What actually ran, and what broke
The repository's Playground API is a useful test surface. It exposes fixed charges, x402 upto usage billing, MPP subscriptions, and a session stream against the hosted Solana Payment Sandbox. The API exposes unpaid discovery and documentation as well.
The browser Playground did not install cleanly on the first attempt. Its app package asked for x402-svm-2.16.0.tgz. The repository's vendor recipe produced x402-svm-2.16.0-paykit.2.tgz. I did not edit the repository or silently substitute a dependency. In an isolated checkout I built the external x402 submodule, built MPP first, and started the API package separately.
The checkout was commit 358926f025459369f250091acfb5b911ccc1f1f3, and the PayKit package was version 0.7.0. I started the API with pnpm -C typescript/examples/playground-api start. The x402 reference was 5JQov3btV1AreDmgrHmdRgtnGWQgr3KGakGNy4FQg5A5B35ddYqmn7NUQSrCEKBzSXQNVvZTMST1yStde5uhuvzF. The MPP references were 3Y7xANWu7hFZaBSuCtexxdouK9nVaew1PBk6RCtCdqdW4gaY71k5JMswc9G6wDc4Vdgc27TnmQpKhajNB69SwqJp and fo7TWRmBwEahZrKd25jyDfmRaQhf4ZDhBPFaZLJ6drbhnc5vVk3FrCx9GW5MuG2viAzPmoFXLFaDShUE1GX9h78. RPC rereads returned slots 436078484, 436078475, and 436078479, with err: null for all three.
The API-side run then gave me these receipts:
- An unpaid
fortuneresponse with both x402 exact and MPP charge offers. - An unpaid
jokeresponse with the seller and platform split and no x402 offer. - An unpaid
summarizeresponse with an x402uptoceiling of $0.10. - A PayKit client completing the same fortune request through MPP and x402.
- MPP and x402 settlement references that were present in the sandbox and had
err: nullwhen reread.
The README's language table lists server or client support across TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin, and Swift. That breadth is useful if your service is not written in TypeScript. It is not a substitute for installing each language's package and checking the actual release artifact. Today's filename mismatch is small, but payment libraries fail at small seams.
Four things I did not measure
This sandbox run did not measure a payer-side Solana network fee. The 402 challenge carried a feePayer, and the API run used the operator signer as the fee payer. I also found no PayKit usage fee in the MIT-licensed repository. That is not a production cost model. Chain fees, RPC fees, and operating costs still need a real measurement under the intended network conditions.
I did not run the case where the payment settles and the application then returns 500, or the case where a retry creates a duplicate payment intent. PayKit handles the 402 payment boundary. It does not decide an application's idempotency, refunds, or retry policy. A production service needs its own intent ID and duplicate-spend protection.
The direct operational answer is uncomfortable but clear: if settlement succeeds and the handler then returns 500, the transfer is already on chain. This run did not test a refund or recovery path, and PayKit does not automatically undo that transfer. The application must persist an intent ID, make a retry reread that intent before signing again, and let the service define its refund policy. Otherwise a retry can become a second payment.
The wallet in my run was disposable and funded with 100 sandbox USDC. Production still needs key custody, per-call and daily caps, low-balance stops, and an error path for a wrong recipient. The README lists nine language surfaces, but I only ran the TypeScript API and client through an error-free payment cycle. The filename mismatch is why I would test each language before calling it production-ready.
The direct answers are deliberately limited. I did not measure any cost beyond the $0.01 API charges, so I am not claiming a final margin. I did not test an application failure after settlement or duplicate intent on retry, so PayKit is not my answer for refunds or idempotency. The service operator, not the reader, must choose key custody and spending caps. The evidence here supports one claim only: the sandbox payment round trip worked.
Should an agent use this?
Yes, when the agent calls a known set of APIs, each call has a small price, and the wallet has a spending limit. The protocol turns a rejected request into a machine-readable challenge. The client signs the requested payment and retries the same request. There is no invoice email to parse and no browser checkout to click.
No, when the product needs the guarantees people associate with cards. Stablecoin settlement is final. A lost key is not a forgotten password. A bad recipient is not a chargeback case. PayKit gives the server a payment boundary, not a complete money-safety policy.
The practical order is simple: run the 402 handshake in a sandbox, verify the returned body, reread the settlement on the chain, then decide how to store keys and cap spending. Funding a production wallet before proving those four steps is backwards.
The useful conclusion
The interesting result was not that AI agents are about to become companies. It was smaller and more testable. An HTTP client saw 402, signed a stablecoin payment, replayed the same request, and received 200. I did it twice with two different rails. The sandbox recorded both settlements.
x402 is the cleaner one-recipient charge. MPP is the better fit when the payment carries splits, intent, and receipt metadata. PayKit gives an application one gate surface for both, while still letting the protocol differences show up where they matter.
I trust the result because I also kept the broken install in the story. Payment infrastructure is exactly where a polished success path can mislead you.
出典
Try the test conditions: experiment page



Top comments (0)