Ask an AI agent to read your inbox, search the web, summarize a PDF or write some code, and it will. Ask it to buy you a video game and it stalls. The model could handle the task fine. The trouble is that commerce was built around a human at checkout, with shopping carts, address forms, 3-D Secure popups and captchas, and an agent has no way through any of that.
So here is a working counterexample. We built an agent that buys any of 40,000+ video game keys autonomously and pays in stablecoins and other crypto (USDC, USDT, EURC) on Base chain through an x402-style payment flow. It runs end to end with no accounts, no API keys and nobody in the loop, and the agent code comes to about 200 lines.
The repo is public, and you can run the whole thing end-to-end on Base Sepolia testnet today: github.com/cdkbotmcp/cdk-agent-example.
The two-step problem with agent commerce
Any real purchase needs two integrations. You have to find the product, and then you have to pay for it. For agents, today's APIs handle both badly.
Catalog APIs tend to assume a person is browsing. You get paginated lists, marketing copy stuffed into titles, and regional prices that depend on geo-IP. An agent trying to pick the cheapest legitimate offer for one platform and one region ends up scraping and reconciling mismatched data on its own.
Payment is the harder half. Stripe and similar processors grew up around forms, billing addresses and 3-D Secure flows, and an agent can complete none of those. Crypto gets around them, but usually through a wallet UX where a human approves each step. What agent-native commerce needs is a payment the agent can finish with zero human interaction and the merchant can verify deterministically.
We ended up needing two pieces:
- An API designed for agents, with structured filters, one product per request, and live competitor prices in the response so the agent can rank offers without scraping.
- A payment protocol that lives in HTTP, which is what x402 is. The server answers with HTTP 402 and payment instructions, the client sends crypto, and the server verifies it onchain before handing back the product.
What x402 actually does
Of all the agentic infrastructure we've seen in the last year, x402 is the cleanest. Mechanically it is HTTP status 402, "Payment Required", plus a structured response telling the client how to pay.
The fast path is a single call. The agent POSTs to /purchase with an X-PAYMENT header. That header holds a base64-encoded EIP-3009 "exact" transferWithAuthorization, signed by the buyer's wallet against the accepts[] the server advertises. A facilitator settles it on-chain gaslessly, so the buyer pays no gas, and the product key comes back in the same response. You don't need a tx_hash, a second round trip or a quote_id:
Agent Server Base chain
│ │ │
├── POST /purchase ─────────→ │ │
│ { "game_id": "..." } │ │
│ X-PAYMENT: │ facilitator settles ──→ │
│ │ (gasless, on-chain) │
│←── 200 { key_code } ─────────┤ │
The agent signs once, makes one HTTP call and has the key. It never broadcasts a transaction or pays gas itself.
The two-step flow is the universal fallback. Use it when a wallet or agent would rather send the USDC on its own. It takes three HTTP calls and one onchain transaction:
Agent Server Base chain
│ │ │
├── POST /purchase ─────→ │ │
│ { "game_id": "..." } │ │
│←── 402 + pay details ────┤ price locked (5 min) │
│ address, amount, │ │
│ token, chain, quote_id │ │
│ │ │
├── ERC-20 transfer ─────────────────────────────→ │
│ USDC to address │ │
│ │ │
├── POST /purchase ─────→ │ │
│ + tx_hash + quote_id │ verify on-chain ────→ │
│ │ (5 block confirmations) │
│←── 200 { key_code } ─────┤ │
Nowhere in that flow is there an account, a webhook or a callback URL. The quote_id locks the price for 5 minutes, so it can't drift between browsing and paying. Once the payment lands, the server reads the sender wallet from the Transfer event and binds it to the order. From then on, only the wallet that paid can fetch the key or ask for a refund.
If you care about x402 terminology: the one-shot is the canonical x402 "exact" scheme, where the client signs an EIP-3009 authorization and a facilitator settles it. The two-step fallback is a variant in which the agent broadcasts the USDC transfer itself and we verify it onchain. Both use the same HTTP-402 shape and neither needs a human, so go with whichever your wallet supports.
The agent code
The quickest route to a key is the cdkbot CLI on npm. It runs the whole one-shot for you, building the signed authorization and posting it with the X-PAYMENT header, so buying takes one command:
cdkbot buy <game_id> --key <privateKey>
Behind that command is a single POST to /purchase with the signed EIP-3009 authorization attached, and the response carries the key. Wiring the same one-shot into your own agent comes down to one function:
def buy_oneshot(game_id: str, wallet) -> str:
# 1. Get the 402 + its accepts[] (what to sign)
quote = requests.post(f"{API}/purchase",
json={"game_id": game_id})
accepts = quote.json()["accepts"][0]
# 2. Sign an EIP-3009 transferWithAuthorization for it
x_payment = build_x_payment(wallet, accepts) # base64 signed auth
# 3. Post it back with X-PAYMENT — facilitator settles gaslessly,
# key returns in the same call. No tx_hash, no quote_id.
result = requests.post(f"{API}/purchase",
json={"game_id": game_id},
headers={"X-PAYMENT": x_payment}).json()
return result["key_code"]
The two-step fallback (broadcast the USDC yourself, then confirm with tx_hash) isn't much longer. Below is the Python version of agent.py. Take out the boilerplate and this is the whole fallback flow:
def buy(title: str) -> str:
# 1. Find best product match
product = requests.get(f"{API}/games/match",
params={"title": title, "platform": "Steam"}).json()["best_match"]
# 2. Get price quote (HTTP 402)
quote = requests.post(f"{API}/purchase",
json={"game_id": product["id"], "chain": "base-sepolia"}).json()
# 3. Send USDC on-chain
tx_hash = send_usdc_transfer(
to=quote["payment"]["address"],
amount=quote["payment"]["amount"],
)
# 4. Confirm + receive key
result = requests.post(f"{API}/purchase", json={
"game_id": product["id"],
"tx_hash": tx_hash,
"quote_id": quote["quote_id"],
"chain": "base-sepolia",
}).json()
return result["key_code"]
Wrap that in asyncio or give it to a tool-calling LLM and you have an agent that buys games. There's a TypeScript version too. Its structure is the same, and it uses viem for the onchain transfer.
Three details that mattered more than we expected
1. Live competitor prices in the catalog response
Each GET /games/{id} response carries a marketplace_offers array listing the cheapest current offer from major keyshops, in USD. Putting rivals' prices in our own response sounds odd. But the first thing an agent wants to know is whether the price is any good, and if we don't tell it, it goes off to scrape. Saving it that trip is what separates an integration that takes 10 minutes from one that takes two weeks.
There's a knock-on effect. An agent using CDK Bot can rank offers by price and seller trust across the whole market in one HTTP call. A site built for people could almost never offer that, because no human shopping site wants to show off its competitors. An agent-native API runs on different incentives.
2. Price locking via quote_id
Wholesale game prices move. Say an agent is quoted $24.99 and sends $24.99 in USDC, and in between the price ticks up to $25.49. Who covers the gap? We issue a quote_id with the 402 response, good for 5 minutes, so nobody does. The price was locked. Without that, every onchain confirmation would turn into a haggle.
3. The sender-wallet-binds-order rule
When a payment arrives, we take the from address out of the ERC-20 Transfer event and bind it to the order for good. Only that wallet can retrieve the key. Only that wallet can request a refund. That closes off a whole class of front-running attack: someone who spots the tx_hash onchain still can't claim the key, since they have no quote_id and they aren't the wallet that sent the money.
Zero-code option: Claude Desktop + MCP
Maybe 200 lines of Python is more than you want to write. We also wrapped the same flow in an MCP (Model Context Protocol) server with 8 tools, so you can paste a config into Claude Desktop and talk to the catalog directly:
{
"mcpServers": {
"cdk": {
"url": "https://mcp.cdk.bot/mcp",
"transport": "streamable-http"
}
}
}
Restart Claude, then type "search for Hollow Knight Silksong and quote me a price." Claude calls search_games natively (it's the MCP counterpart of the REST /games/match call above), followed by get_price_quote. Cursor and Cline take the same config. There's nothing to install and no API key to manage.
What this unlocks
Writing an agent that buys one game is the less interesting problem. We care more about what new things become possible once commerce runs over HTTP in a form agents can read. (We made the bigger-picture case in a separate post: why agents won't buy pizza first.) Some examples:
- A holiday-gift agent that runs unattended overnight, buying 12 different Steam keys for your friends and emailing each of them a code.
- A market-watch agent that keeps an eye on a wishlist and buys once a price drops past your threshold.
- A travel-companion agent that picks up a Netflix or Spotify gift card in the local currency when you land in a new country.
- A budget-coach agent that pools spare USDC from the various agents you run and spends it on categories you've approved in advance.
Two years ago none of these could exist, because neither the payment layer nor the product layer could be driven by an agent. Both can be now. The infrastructure is also small: one signed call for the gasless one-shot, or three HTTP calls for the two-step fallback. Any of these is a weekend project.
Try it
The repo has Python, TypeScript and MCP examples: github.com/cdkbotmcp/cdk-agent-example. It defaults to Base Sepolia testnet, so you can run the full flow without spending real money, and testnet USDC is free from Circle's faucet.
The API docs and live OpenAPI spec are at api.cdk.bot/docs. Other agents can find us programmatically through the discovery card at /.well-known/agent.json.
If you have questions, an odd use case, or a product category you'd like us to add, write to info@cdk.bot.
