Use this file to discover all available pages before exploring further.
Start here: need to register a service and create a plan first? Follow the
5-minute setup.
Runnable tutorial
langchain-paid-agent-py — a deliberately minimal LangChain + LangGraph agent
with a single tool gated by @requires_payment. Clone, fill in .env, run
poetry run buyer to see the full discovery → token-acquisition → settlement
flow in five numbered steps. The cleanest starting point if you’d rather read
working code than docs.
In LangChain.js, requiresPayment() is a higher-order function that wraps the tool implementation:
import 'dotenv/config'import { tool } from '@langchain/core/tools'import { z } from 'zod'import { Payments } from '@nevermined-io/payments'import { requiresPayment } from '@nevermined-io/payments/langchain'const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT || 'sandbox',})const PLAN_ID = process.env.NVM_PLAN_ID!// Protect a tool with payment — 1 credit per callconst searchData = tool( requiresPayment( (args) => `Results for '${args.query}': ...`, { payments, planId: PLAN_ID, credits: 1 } ), { name: 'search_data', description: 'Search for data on a given topic. Costs 1 credit.', schema: z.object({ query: z.string() }), })
The payment token is read from config.configurable.payment_token.
Pass it when invoking the tool or agent.
In Python, @requires_payment is a decorator applied before @tool:
import osfrom dotenv import load_dotenvfrom langchain_core.runnables import RunnableConfigfrom langchain_core.tools import toolfrom payments_py import Payments, PaymentOptionsfrom payments_py.x402.langchain import requires_paymentload_dotenv()payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ))PLAN_ID = os.environ["NVM_PLAN_ID"]# Protect a tool with payment — 1 credit per call@tool@requires_payment(payments=payments, plan_id=PLAN_ID, credits=1)def search_data(query: str, config: RunnableConfig) -> str: """Search for data on a given topic. Costs 1 credit.""" return f"Results for '{query}': ..."
The tool function must accept a config: RunnableConfig parameter.
The decorator uses it to read the payment token from
config["configurable"]["payment_token"].
import { HumanMessage } from '@langchain/core/messages'import { ChatOpenAI } from '@langchain/openai'const llm = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 })const tools = [searchData, summarizeData, researchTopic]const llmWithTools = llm.bindTools(tools)const toolMap = new Map(tools.map((t) => [t.name, t]))const messages = [new HumanMessage('Search for AI trends')]const aiMessage = await llmWithTools.invoke(messages)messages.push(aiMessage)for (const toolCall of aiMessage.tool_calls || []) { const result = await toolMap.get(toolCall.name)!.invoke( toolCall.args, { configurable: { payment_token: accessToken } } )}
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)tools = [search_data, summarize_data, research_topic]llm_with_tools = llm.bind_tools(tools)tool_map = {t.name: t for t in tools}messages = [HumanMessage(content="Search for AI trends")]ai_message = llm_with_tools.invoke(messages)messages.append(ai_message)for tool_call in ai_message.tool_calls: result = tool_map[tool_call["name"]].invoke( tool_call["args"], config={"configurable": {"payment_token": access_token}}, )
The same payment-protected tools work with LangGraph’s create_react_agent. The simple case — buyer already holds a token — just threads it through configurable:
TypeScript
Python
import { createReactAgent } from '@langchain/langgraph/prebuilt'const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4o-mini' }), tools: [searchData, summarizeData, researchTopic], prompt: 'You are a helpful research assistant.',})const result = await agent.invoke( { messages: [{ role: 'human', content: 'Research AI agents and summarize' }] }, { configurable: { payment_token: accessToken } })
from langgraph.prebuilt import create_react_agentgraph = create_react_agent( ChatOpenAI(model="gpt-4o-mini"), [search_data, summarize_data, research_topic], prompt="You are a helpful research assistant.",)result = graph.invoke( {"messages": [("human", "Research AI agents and summarize")]}, config={"configurable": {"payment_token": access_token}},)
Discovery-first flow with create_paid_react_agent (Python)
If the buyer doesn’t know the plan id / scheme / provider up front, the x402 way is to invoke the agent without a token, let the protected tool raise PaymentRequiredError, and read the requirements off the exception. By default LangGraph’s ToolNode would catch that exception and stringify it into a ToolMessage for the LLM — losing the X402PaymentRequired payload. The SDK ships create_paid_react_agent for exactly this: it builds the underlying ToolNode with handle_tool_errors=False, so the exception propagates to agent.invoke()’s caller with the payload intact.
from payments_py.x402.langchain import ( PaymentRequiredError, create_paid_react_agent, requires_payment,)from payments_py.x402.types import DelegationConfig, X402TokenOptions@tool@requires_payment(payments=payments, plan_id=PLAN_ID, credits=1)def get_market_insight(topic: str, config: RunnableConfig = None) -> str: """Return a market insight. Costs 1 credit.""" return f"Market insight for '{topic}': ..."agent = create_paid_react_agent( ChatOpenAI(model="gpt-4o-mini", temperature=0), [get_market_insight], prompt="You are a market data assistant.",)# 1. Discover what the agent's tool charges by invoking without a token.try: agent.invoke({"messages": [("human", QUERY)]}, config={"configurable": {}})except PaymentRequiredError as err: accept = err.payment_required.accepts[0] # accept.scheme → "nvm:erc4337" or "nvm:card-delegation" # accept.network → CAIP-2 chain or provider name (stripe, braintree, …) # accept.plan_id → which plan to acquire a token against# 2. Pick a payment method matching the discovered network.pm = next(m for m in payments.delegation.list_payment_methods() if m.provider == accept.network)# 3. Acquire the token against the discovered plan.token = payments.x402.get_x402_access_token( accept.plan_id, token_options=X402TokenOptions( scheme=accept.scheme, delegation_config=DelegationConfig( provider_payment_method_id=pm.id, spending_limit_cents=10000, duration_secs=3600, currency="usd", ), ),)["accessToken"]# 4. Retry with the token.result = agent.invoke( {"messages": [("human", QUERY)]}, config={"configurable": {"payment_token": token}},)
For card-delegation plans the SDK needs a DelegationConfig so it can
auto-create the Stripe / Braintree delegation that backs the payment
signature. Crypto plans (nvm:erc4337) need the same — see the x402
Protocol module reference for the
full token-options surface.
After a successful agent call, the buyer can read the SettleResponse — credits_redeemed, remaining_balance, transaction, network, payer — via last_settlement():
last_settlement() reads from a module-level slot. In multi-tenant processes
(e.g. a server handling concurrent settlements), the value reflects whichever
invocation settled most recently — there is no per-call isolation. For
multi-tenant scenarios, surface settlement via a callback or observability
layer instead.
Once @requires_payment is wired up, you can have every paid tool call surface as structured spans in LangSmith — no code changes required, just two env vars and an optional extra.Install the optional extra:
pip install "payments-py[langchain,langsmith]"
Enable tracing:
LANGSMITH_TRACING=trueLANGSMITH_API_KEY=lsv2_pt_your-keyLANGSMITH_PROJECT=nvm-langchain # optional# Only needed if your LangSmith account is NOT in GCP US:# LANGSMITH_ENDPOINT=https://eu.api.smith.langchain.com
That’s it. Running the same agent invocation now produces a trace tree with two dedicated Nevermined child spans nested under the tool:
LangGraph└── tools └── get_market_insight ├── nvm:verify 0.28s ← around payments.facilitator.verify_permissions └── nvm:settlement 1.88s ← around payments.facilitator.settle_permissions
Each Nevermined span carries nvm.* metadata for audit + reconciliation:
The same nvm.* metadata is also attached to the parent tool span so cmd-F searches in the LangSmith UI land on either level.Failed discovery probes are first-class too. When the buyer’s first agent.invoke() runs without a payment_token (the discovery-first flow), the nvm:verify span still opens, carries the static nvm.plan_ids / nvm.scheme / nvm.network, and is marked failed by the raised PaymentRequiredError. That gives you “which plan was the probe against?” filterability instead of an opaque LangChain crash.
The payment_token the buyer passes in config["configurable"]["payment_token"] would normally be captured into the parent tool span’s metadata by LangChain and inherited by every child span. The full token grants access to the protected tool until it expires. @requires_paymentproactively strips it from the parent span’s metadata before opening any nvm:* child, so the full credential never reaches a Nevermined-emitted attribute. The abbreviated nvm.payment_token (first 16 chars + … + last 4 chars) remains available for correlation.Other channels (custom callbacks, an explicit add_metadata({"payment_token": ...}), tool signatures that contain the token) are not covered — strip them yourself or set export LANGSMITH_HIDE_INPUTS=true for blanket coverage.
If a span failure ever occurs during metadata building or attachment, observability is silently dropped — the payment flow itself is never interrupted. Settlement receipts persist via last_settlement() regardless of whether the span emit succeeded.For the full module reference (function signatures, manual usage from non-LangChain code paths), see the Python LangChain module reference.
The credits argument is sent to the facilitator as max_amount. The amount
actually redeemed depends on the plan’s server-side credit config:
fixed plans (where plan.credits.minAmount == plan.credits.maxAmount)
always burn plan.credits.maxAmount and ignore the supplied value (per
nvm-monorepo#1568);
range plans clamp the value into [minAmount, maxAmount]. If you want
predictable per-call cost, configure the plan as fixed.
For serving the agent over HTTP, use payment middleware on your framework. Payment is handled at the HTTP layer — tools are plain functions with no decorators or payment config.
# Explicit card-delegation scheme for fiat payments@tool@requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, scheme="nvm:card-delegation",)def my_fiat_tool(query: str, config: RunnableConfig) -> str: ...
The decorator/wrapper automatically detects the payment scheme from plan metadata.
Plans with fiat pricing (isCrypto: false) use nvm:card-delegation (Stripe).
No code changes are needed on the agent side.