Give a Vercel AI SDK agent a tool it pays for per call.
A working Vercel AI SDK tool that calls a live Apiosk endpoint. The model picks the tool and reads the result; the x402 client underneath answers the 402 and signs a USDC payment on Base per call. No API key, no account.
npm install ai @ai-sdk/anthropic zod @x402/axios @x402/evm axios viemWhat you are about to do
The whole integration is one tool definition and one wallet key. Everything specific to Vercel AI SDK is below; everything specific to payment happens inside the client.
Install the SDK and a paying HTTP client
npm install ai @ai-sdk/anthropic zod @x402/axios @x402/evm axios viem. Put a funded Base wallet key in WALLET_PRIVATE_KEY. Nothing to sign up for, so there is no API key to add.
Define the tool with inputSchema
Wrap the endpoint in tool() from the ai package. Describe the arguments with zod under inputSchema, and let execute call the gateway through the wrapped axios instance. The x402 interceptor handles the 402 and the retry, so execute reads like an ordinary request.
Hand the tool to generateText
Pass it in the tools map on generateText or streamText and bound the loop with stopWhen: isStepCount(n). The model decides when the job needs the tool, calls it with its own arguments, and reads the returned JSON on the next step.
Vercel AI SDK, end to end
Copy these in order. The endpoints are placeholders — swap in any endpoint from the catalog.
Three things to know
The key lives in the client, not the tool
WALLET_PRIVATE_KEY is read once, where the axios instance is built. Tools import that instance. The model sees the description and the inputSchema and nothing else: not the wallet, not the price, not the payment header. Keep the client in its own module so a second tool cannot reconstruct it with different settings.
execute is async because the call is two round trips
A paid request is a 402 with a price, then a signed retry. Both happen inside execute, so the promise resolves later than an unpaid fetch would. Return the parsed JSON, not the axios response: whatever you return is what the model reads on the next step, and a response object serialises into noise.
stopWhen is the ceiling on a run
maxSteps is gone. stopWhen: isStepCount(n) bounds how many tool round trips one generateText or streamText can make, which is the upper bound on paid calls per run. Leave it out and a loop that keeps calling the tool keeps paying. stepCountIs still resolves as an alias if you have it in older code.
Frequently asked questions
Why does the tool use inputSchema instead of parameters?
parameters was renamed to inputSchema in AI SDK v5, and it is a required field on the Tool interface in v7. The string parameters no longer appears in the ai package types. The failure is loud rather than quiet: tool() has no overload that accepts an object without inputSchema, so TypeScript rejects the call and the execute arguments fall back to an implicit any on top of it. Any snippet you find with parameters predates v5.
Does the model decide how much money to spend?
No. The model chooses which tool to call and with what arguments. Everything after that is your code: the x402 client reads the price from the 402 response, signs a USDC payment on Base with the key you gave it, and retries. The model never sees the amount and never approves it. The two levers you hold are stopWhen, which caps how many tool round trips one run can make, and the caps on the wallet you sign with.
Does this work with streamText and other model providers?
Yes. A tool built with tool() is provider-independent, so the same object passes to streamText, to generateObject flows that call tools, and to any @ai-sdk provider package. Only the model line changes. One v7 detail if you are adapting an older snippet: system was renamed to instructions on the call options, and system is now marked deprecated. prompt is unchanged.
Every listed API, on the same tool interface.
Agents call the comparison. Providers get into it.