Connect · CrewAI

A CrewAI agent that buys the data it needs.

A CrewAI tool that calls a paid endpoint on gateway.apiosk.com. The 402 is answered inside the tool body, so the agent asks a question and gets JSON back. No API key, no account.

pip install crewai "x402[requests,evm]"
Three steps

What you are about to do

The whole integration is one tool definition and one wallet key. Everything specific to CrewAI is below; everything specific to payment happens inside the client.

01

Install crewai and the x402 requests client

pip install crewai "x402[requests,evm]". Custom tools ship inside crewai itself, so you do not need the [tools] extra — that one only pulls the prebuilt crewai_tools catalog. The evm extra brings eth-account and web3 with it. CrewAI needs Python 3.10 or newer.

02

Define the tool with @tool from crewai.tools

Build one paying requests.Session at module level, then decorate a plain function with @tool. CrewAI turns the type annotations and the docstring into the schema the model reads, so both are required — it raises ValueError without them. Reach for BaseTool with args_schema when you want to write the argument descriptions yourself.

03

Hand it to the Agent and kick off the Crew

Pass the decorated function into Agent(tools=[...]), give the Task an expected_output, then call Crew(agents=[...], tasks=[...]).kickoff(). The Agent's own llm defaults to gpt-4.1-mini, so set OPENAI_API_KEY or pass llm=. kickoff returns a CrewOutput; result.raw is the answer.

The code

CrewAI, end to end

Copy these in order. The endpoints are placeholders — swap in any endpoint from the catalog.

tools/convert_currency.pyPython
# tools/convert_currency.py
# pip install crewai "x402[requests,evm]"

import os

from crewai.tools import tool
from eth_account import Account
from x402 import x402ClientSync
from x402.http.clients.requests import x402_requests
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact.register import register_exact_evm_client

# The x402 client only signs payments. x402_requests turns it into a plain
# requests.Session that answers the 402 and retries the call. Build it once at
# import time; every tool below reuses it.
account = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])
payments = x402ClientSync()
register_exact_evm_client(payments, EthAccountSigner(account))
session = x402_requests(payments)


@tool("Convert currency")
def convert_currency(from_code: str, to_code: str, amount: float) -> dict:
    """Convert an amount between two currencies using live FX rates.

    Paid per call in USDC on Base. No API key required.
    """
    response = session.get(
        "https://gateway.apiosk.com/v1/fx/convert",
        params={"from": from_code, "to": to_code, "amount": amount},
    )
    response.raise_for_status()
    return response.json()
tools/verify_email.pyPython
# tools/verify_email.py
# The subclass form. Use it when you want to name and describe the arguments
# yourself instead of letting CrewAI read them off the signature.

from crewai.tools import BaseTool
from pydantic import BaseModel, Field

from tools.convert_currency import session  # one paying session for every tool


class VerifyEmailInput(BaseModel):
    email: str = Field(description="Address to check, e.g. ada@example.com")


class VerifyEmailTool(BaseTool):
    name: str = "Verify email"
    description: str = (
        "Check whether an email address is deliverable. "
        "Returns the verdict and the reason for it."
    )
    args_schema: type[BaseModel] = VerifyEmailInput

    def _run(self, email: str) -> dict:
        response = session.get(
            "https://gateway.apiosk.com/v1/email/verify",
            params={"email": email},
        )
        response.raise_for_status()
        return response.json()
crew.pyPython
# crew.py
# Run with: python crew.py

from crewai import Agent, Crew, Task

from tools.convert_currency import convert_currency
from tools.verify_email import VerifyEmailTool

# @tool returns a Tool, which is a BaseTool, so both forms go in the same list.
analyst = Agent(
    role="Operations analyst",
    goal="Answer questions that need live data",
    backstory="You check numbers with a source before you report them.",
    tools=[convert_currency, VerifyEmailTool()],
    verbose=True,
)

task = Task(
    description="What is 250 EUR in USD right now?",
    expected_output="The converted amount and the rate used.",
    agent=analyst,
)

result = Crew(agents=[analyst], tasks=[task]).kickoff()
print(result.raw)  # kickoff returns a CrewOutput; .raw is the string answer
Before your first call

Three things to know

The key stays out of the crew

WALLET_PRIVATE_KEY is read once when the tool module imports, and the session is closed over by the function body. It never enters an Agent, a Task description or a tool argument, so it cannot reach the model's context or the crew log.

Sync tools, sync client

CrewAI executes a tool body synchronously, so pair x402ClientSync with x402_requests. The async pair — x402Client with httpx — raises TypeError here. x402_requests hands back an ordinary requests.Session, so params=, raise_for_status() and `with` all behave as usual.

The agent sees one tool call

The 402, the signature and the retry all happen inside session.get. The tool returns the JSON body and nothing else, so with verbose=True the crew log shows a single tool call rather than two requests. The settled call leaves an on-chain receipt on Base.

FAQ

Frequently asked questions

Where does BaseTool live now — crewai.tools or crewai_tools?

crewai.tools. Both `from crewai.tools import tool` and `from crewai.tools import BaseTool` are current and ship in the crewai package itself. The older `from crewai_tools import BaseTool` no longer resolves: crewai_tools is now only a catalog of prebuilt tools, and the `crewai[tools]` extra installs it. Writing your own tool needs nothing beyond crewai.

Do I use x402Client or x402ClientSync in a CrewAI tool?

x402ClientSync, paired with `x402_requests`. A CrewAI tool body runs synchronously — `_run` and the function under @tool are both sync — so the requests-based surface is the match. x402Client is the async half and pairs with `x402HttpxClient` from x402[httpx]. Mixing the two raises TypeError by design. There is no x402-httpx package on PyPI; httpx and requests are extras of the single `x402` package.

Does the tool need an API key for the endpoint?

No. gateway.apiosk.com replies 402 Payment Required with the price for that request, the session signs a USDC payment on Base, and the call is retried. There is no key, no account and no invoice for the endpoint. The agent's own model is separate: CrewAI's Agent defaults to gpt-4.1-mini, so set OPENAI_API_KEY or pass `llm=` if you use another provider.

Every listed API, on the same tool interface.

Agents call the comparison. Providers get into it.