Connect · LangChain

A LangChain tool that pays for its own call.

Wrap any endpoint on gateway.apiosk.com in a @tool your Python agent can call. The tool body makes one request; the 402, the USDC payment on Base and the retry all happen inside the session.

pip install "x402[requests,evm]" langchain-core
Three steps

What you are about to do

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

01

Install x402 with the extras

pip install "x402[requests,evm]" langchain-core. The requests extra pulls in the sync HTTP path; the evm extra pulls in eth-account and web3, so x402[requests] alone fails at the Account import. Add langchain and langchain-anthropic when you wire up the agent.

02

Define the tool

Build the payment session once at module scope, then decorate a normal function with @tool. The body makes one request against gateway.apiosk.com; the 402, the signature and the retry happen inside session.get.

03

Hand it to create_agent

Pass the tool into create_agent alongside the model. From there the model decides when the job needs an FX rate or an email check, and the call is paid at the moment it is made.

The code

LangChain, 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 "x402[requests,evm]" langchain-core

import os

from eth_account import Account
from langchain_core.tools import tool
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

account = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])

# x402ClientSync is the payment client, not an HTTP client. Register a signer
# against it, then hand it to x402_requests to get a requests.Session that
# answers the 402 on its own.
payments = x402ClientSync()
register_exact_evm_client(payments, EthAccountSigner(account))

session = x402_requests(payments)


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

    Args:
        from_code: ISO currency code to convert from, e.g. EUR.
        to_code: ISO currency code to convert to, e.g. USD.
        amount: Amount to convert.
    """
    response = session.get(
        "https://gateway.apiosk.com/v1/fx/convert",
        params={"from": from_code, "to": to_code, "amount": amount},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()
tools/verify_email.pyPython
# tools/verify_email.py

from langchain_core.tools import tool

# One payment-aware session for every tool. The wallet is built once, in
# tools/convert_currency.py.
from tools.convert_currency import session


@tool(parse_docstring=True)
def verify_email(email: str) -> dict:
    """Check whether an email address is deliverable.

    Args:
        email: The address to check, e.g. ada@example.com.
    """
    response = session.get(
        "https://gateway.apiosk.com/v1/email/verify",
        params={"email": email},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()
agent.pyPython
# agent.py
# pip install langchain langchain-anthropic

from langchain.agents import create_agent

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

agent = create_agent(
    "anthropic:claude-opus-5",
    tools=[convert_currency, verify_email],
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What is 250 EUR in USD right now?"}]}
)

print(result["messages"][-1].content)
Before your first call

Three things to know

The wallet stays in your process

The private key is read from the environment once, at import time, and lives in the module that builds the session. The agent never sees it and never handles a 402. From the model's side a paid call returns the same JSON any other tool would.

A sync tool needs the sync client

A @tool body is called synchronously, so use x402ClientSync with x402_requests. x402Client is the async client; the requests adapter raises TypeError if you hand it one. Build the session at module scope, not inside the tool, so a loop of calls reuses one connection pool.

The docstring is the tool spec

@tool(parse_docstring=True) parses a Google-style Args: block into per-argument descriptions, which is what the model reads when it fills in the call. One naming rule from langchain-core: do not call a tool argument config, run_manager, or callbacks. Those are reserved; take a ToolRuntime parameter if you need runtime access.

FAQ

Frequently asked questions

Should I import tool from langchain.tools or langchain_core.tools?

Either. In LangChain v1 the slim langchain package re-exports tool straight from langchain_core.tools, so both imports give the identical decorator. The current docs write from langchain.tools import tool; the snippets here use langchain_core.tools so the tool file depends only on langchain-core, with no agent package required.

Do I need an API key or an account to call the endpoint from a tool?

No. gateway.apiosk.com replies 402 Payment Required with the price for that request. The session built by x402_requests signs a USDC payment on Base and retries, inside the same session.get call. There is no key to store, no account to register, and no invoice at the end of the month.

Does this work with an async agent?

Not as written. x402ClientSync and x402_requests are the sync pair, and a sync @tool body is what create_agent calls. For an async tool, install "x402[httpx,evm]" and build x402HttpxClient(x402Client()) instead, then define the tool with async def and await the request. Handing an async x402Client to the requests adapter raises TypeError at runtime.

Every listed API, on the same tool interface.

Agents call the comparison. Providers get into it.