Skip to main content
HomeLanding pagesHow to add payments to a Pydantic AI agent
LANDING PAGE

How to add payments to a Pydantic AI agent

7 min read·Last updated June 2, 2026

Pydantic AI is Python and the x402 client ships for Node, so run a small local proxy built on createX402Client from @blockchain0x/x402. Register a Pydantic AI tool that calls the proxy and returns a typed result; when the upstream returns HTTP 402 the proxy settles in USDC on Base. Give each agent its own wallet and set spend limits in the dashboard.

What you will build

A Pydantic AI agent that pays for the tools it calls, settled in USDC on Base, while keeping the typed-result contract Pydantic AI is built around. The agent loop does not change. You add one tool that calls a small local proxy, parse the result into a model, and return it typed. To the agent it is an ordinary validated tool result; underneath, a paid call settled through its wallet.

The honest constraint first: the x402 payment client ships for Node, and Pydantic AI is Python. So the shipped path is a tiny Node proxy that holds the wallet and answers 402 Payment Required, with your tool calling it over localhost. This is the same pattern the CrewAI and LlamaIndex guides use. The Pydantic AI integration page is the broader reference, and the payment API product page covers the wider surface.

Why a Node proxy

A sentence on why the proxy is the right move, not a workaround. The package that answers a 402 and settles in USDC, @blockchain0x/x402, is Node-only today. Inventing a Python import that does not exist would just produce code that fails to install. The proxy is a real Node process doing the real x402 work, reachable from Python over a localhost call your agent already makes. It keeps every payment identifier on the verified surface and costs one extra process per wallet. When a Python x402 client ships, this collapses to a direct call; until then, the proxy is the path that works.

Prerequisites

  • A Blockchain0x account with one agent created, so it has a wallet.
  • A sk_test_ API key for this walkthrough.
  • Node 18+ for the proxy (the SDK targets >=18) and Python 3.9+ for Pydantic AI.
BASH
export B0X_API_KEY=sk_test_...   # sk_test_ -> Base Sepolia

Run the x402 proxy

The only Node you write. It builds a fetch that answers 402s and exposes one route your tool can post a URL to.

JAVASCRIPT
// pay-proxy.mjs  ->  npm i @blockchain0x/node @blockchain0x/x402 && node pay-proxy.mjs
import http from "node:http";
import { createClient } from "@blockchain0x/node";
import { createX402Client } from "@blockchain0x/x402/client";

const sdk = createClient({ apiKey: process.env.B0X_API_KEY }); // sk_test_ pins it to Base Sepolia
const fetchWithPay = createX402Client({ sdk });

http
  .createServer(async (req, res) => {
    const chunks = [];
    for await (const c of req) chunks.push(c);
    const { url } = JSON.parse(Buffer.concat(chunks).toString() || "{}");
    try {
      const upstream = await fetchWithPay(url); // settles the 402 in USDC, retries
      res.writeHead(upstream.status, { "content-type": upstream.headers.get("content-type") ?? "text/plain" });
      res.end(await upstream.text());
    } catch (err) {
      res.writeHead(502).end(String(err));
    }
  })
  .listen(8787, () => console.log("pay-proxy on :8787"));

Start it next to your agent: node pay-proxy.mjs. One proxy per agent wallet, each on its own port with its own B0X_API_KEY.

Register a typed paid tool

In Pydantic AI, a tool is a function registered on the agent. Write one that calls the proxy and register it. No Blockchain0x code lives in Python.

PYTHON
import requests
from pydantic_ai import Agent

agent = Agent("openai:gpt-4o", system_prompt="You are an equity research analyst.")

@agent.tool_plain
def get_quote_realtime(ticker: str) -> str:
    """Fetch a real-time quote for a stock ticker."""
    upstream = f"https://quotes.example.com/v1/quote?ticker={ticker}"
    res = requests.post("http://127.0.0.1:8787", json={"url": upstream}, timeout=30)
    return res.text if res.status_code == 200 else f"Lookup failed: {res.status_code}"

The agent calls get_quote_realtime like any other tool. The proxy settles the upstream 402 in USDC, and the result comes back. The agent never sees the payment.

Keep the result typed

Pydantic AI's whole point is validated, typed data, so do not throw that away at the payment boundary. Parse the proxy's response into a Pydantic model and return the model, not a raw string.

PYTHON
from pydantic import BaseModel

class Quote(BaseModel):
    ticker: str
    price: float

@agent.tool_plain
def get_quote(ticker: str) -> Quote:
    """Fetch a typed real-time quote."""
    upstream = f"https://quotes.example.com/v1/quote?ticker={ticker}"
    res = requests.post("http://127.0.0.1:8787", json={"url": upstream}, timeout=30)
    res.raise_for_status()
    return Quote.model_validate_json(res.text)

Now the agent receives a Quote, validated, with the same guarantees as any other Pydantic AI tool. Payment is an implementation detail inside the function, and the type contract the agent reasons about is unchanged. If the paid call fails, raise_for_status surfaces it as an exception the agent run can handle, rather than feeding the model a malformed string. Keeping the boundary typed is what makes the paid tool feel native to Pydantic AI rather than bolted on.

Validate what you paid for

There is a quieter benefit to keeping the boundary typed, and it is specific to paid tools. When an agent pays for data, you want to know you got what you paid for, not just that a request returned. A 200 with a malformed or empty body is a payment you made for nothing, and a raw-string tool would hand that straight to the model as if it were a real answer.

The Pydantic model is your check. Quote.model_validate_json fails loudly if the paid response does not match the shape you expected, so a provider that quietly changed its format, or returned an error page with a 200, raises instead of polluting the agent's context. You can decide what to do with that failure: retry once against a different provider, return a clear error the planner can act on, or flag it for review. The point is that you paid for a Quote, so you assert you received a Quote, and the type system turns "I hope the paid data is good" into "the paid data is validated or the call fails". For an agent spending real money per call, that guarantee is worth more than it looks.

Set spend limits and test

Set the limit in the dashboard, where the wallet enforces it before any payment, and read it back to confirm:

PYTHON
import os, requests

res = requests.get(
    f"https://api.blockchain0x.com/v1/agents/{os.environ['AGENT_ID']}/spend-permissions",
    headers={"Authorization": f"Bearer {os.environ['B0X_API_KEY']}"},
    timeout=30,
)
permissions = res.json()  # per_tx_wei, allowance_wei, period_seconds, ...

Run the loop on Base Sepolia first with a sk_test_ key: fund the agent's wallet with test USDC from a public faucet, start the proxy, and watch a paid tool call settle. Exercise a refused payment by setting a tight per_tx_wei and confirming an over-limit call surfaces as an error the agent handles. When both paths behave, swap to a sk_live_ key for Base mainnet.

Common pitfalls

Three traps.

Exposing the proxy beyond localhost. It holds a wallet key and pays whatever URL it is handed. Bind it to 127.0.0.1 and treat it like the credential it is.

Dropping the type at the payment boundary. Returning a raw string from a paid tool throws away Pydantic AI's main benefit. Parse into a model and return that.

Putting the budget in the system prompt. A budget in the prompt is a suggestion a crafted input can talk past. The dashboard spend limit is the real boundary, enforced by the wallet before any payment, where the agent cannot reach it.

What to ship today

Start one pay-proxy.mjs with a sk_test_ key, register one typed Pydantic AI tool that calls it, and watch a paid call settle on Base Sepolia. Set a dashboard spend limit before you switch to a sk_live_ key. The CrewAI and LlamaIndex versions of this pattern are at how-to-add-payments-to-crewai-agent and how-to-add-payments-to-llamaindex-agent. Pricing is on the pricing page.

FAQ

Frequently asked questions.

Why a Node proxy instead of a Python package?

The x402 payment client ships for Node, and Pydantic AI is Python. Rather than invent a Python package that does not exist, you run one small Node process that holds the wallet and answers 402s, and your tool calls it over localhost. It is about thirty lines and one process per agent wallet.

How does this fit Pydantic AI's typed model?

Cleanly. The proxy returns text or JSON, and your tool parses it into a Pydantic model before returning, so the agent receives a validated, typed result exactly as it would from any Pydantic AI tool. Payment is an implementation detail inside the tool; the type contract the agent sees is unchanged.

Does it work with agent.tool and agent.tool_plain?

Yes. Both register a function the agent can call, and the payment lives inside that function. Use tool_plain when the tool needs no run context, or tool when it does. The call to the proxy is the same in either case.

Which networks and token does settlement use?

USDC, 6 decimals, on Base. A sk_test_ key settles on Base Sepolia (eip155:84532); a sk_live_ key settles on Base mainnet (eip155:8453). The SDK reads the network from the key prefix. The only payment scheme today is exact-usdc.

How do I bound what the agent spends?

Set the limit in the dashboard. The wallet enforces a per-transaction cap and a per-period allowance before settlement, so a call over either is refused before any USDC moves. Read the active limits with GET /v1/agents/:agentId/spend-permissions.

Create your free agent wallet in 5 minutes.

First payment confirmed in under ten minutes. Free to start.