Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.nevermined.app/llms.txt

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.
Add payment protection to LangChain and LangChain.js tools using the x402 protocol. The library provides two complementary approaches:
ApproachBest forPayment layer
requiresPayment wrapper/decoratorDirect tool invocation, CLI scripts, notebooksPer-tool wrapper
Payment middleware on HTTP serverServing the agent over HTTPHTTP middleware
Both use the same Nevermined plan, credits, and settlement flow — choose whichever fits your deployment model.

Installation

npm install @nevermined-io/payments @langchain/core @langchain/openai zod
The @nevermined-io/payments/langchain sub-path export provides the requiresPayment() wrapper. For the HTTP server approach, also install express.

Approach 1: Tool Decorator / Wrapper

x402 Payment Flow (decorator)

Quick Start

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 call
const 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.

Invoking with payment

import { Payments } from '@nevermined-io/payments'

// Subscriber side — acquire token
const subscriber = Payments.getInstance({
  nvmApiKey: process.env.NVM_SUBSCRIBER_API_KEY!,
  environment: process.env.NVM_ENVIRONMENT || 'sandbox',
})

const token = await subscriber.x402.getX402AccessToken(PLAN_ID)
const accessToken = token.accessToken

// Invoke tool directly
const result = await searchData.invoke(
  { query: 'AI trends' },
  { configurable: { payment_token: accessToken } }
)

LLM-driven tool calling

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 } }
  )
}

LangGraph ReAct agent

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:
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 } }
)

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.

Reading the settlement receipt

After a successful agent call, the buyer can read the SettleResponsecredits_redeemed, remaining_balance, transaction, network, payer — via last_settlement():
from payments_py.x402.langchain import last_settlement

result = agent.invoke(
    {"messages": [("human", QUERY)]},
    config={"configurable": {"payment_token": token}},
)
receipt = last_settlement()
if receipt:
    print(f"credits redeemed: {receipt.credits_redeemed}")
    print(f"remaining balance: {receipt.remaining_balance}")
    print(f"tx: {receipt.transaction}")
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.

Trace payments in LangSmith (Python)

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=true
LANGSMITH_API_KEY=lsv2_pt_your-key
LANGSMITH_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:
SpanAttributes
nvm:verifynvm.plan_ids, nvm.scheme, nvm.network, nvm.agent_id, nvm.payer, nvm.agent_request_id, nvm.payment_token (abbreviated), nvm.verify.duration_ms
nvm:settlementnvm.credits_redeemed, nvm.balance.after, nvm.tx_hash, nvm.network, nvm.payer, nvm.payment_token (abbreviated), nvm.settle.duration_ms
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_payment proactively 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.

Dynamic Credits

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.
Three patterns for credit calculation:
// Pattern 1: Static number — always costs 1 credit
const searchData = tool(
  requiresPayment(
    (args) => `Results for ${args.query}`,
    { payments, planId: PLAN_ID, credits: 1 }
  ),
  { name: 'search_data', description: '...', schema: z.object({ query: z.string() }) }
)

// Pattern 2: Arrow function — cost scales with output length
const summarize = tool(
  requiresPayment(
    (args) => `Summary of ${args.text}`,
    {
      payments, planId: PLAN_ID,
      credits: (ctx) => Math.max(2, Math.min(Math.floor(String(ctx.result).length / 100), 10)),
    }
  ),
  { name: 'summarize', description: '...', schema: z.object({ text: z.string() }) }
)

// Pattern 3: Named function — complex logic on args + result
function calcCredits(ctx: { args: Record<string, unknown>; result: unknown }): number {
  const topic = String(ctx.args.topic || '')
  const result = String(ctx.result || '')
  const base = 3
  const keywordExtra = Math.max(0, topic.split(' ').length - 3)
  const outputExtra = Math.floor(result.length / 200)
  return Math.min(base + keywordExtra + outputExtra, 15)
}

const research = tool(
  requiresPayment(
    (args) => `Report on ${args.topic}`,
    { payments, planId: PLAN_ID, credits: calcCredits }
  ),
  { name: 'research', description: '...', schema: z.object({ topic: z.string() }) }
)
The credits function receives { args, result } after tool execution.

Approach 2: HTTP Server with Payment Middleware

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.

x402 Payment Flow (HTTP)

Server: LangChain

import 'dotenv/config'
import express from 'express'
import { HumanMessage, ToolMessage } from '@langchain/core/messages'
import { tool } from '@langchain/core/tools'
import { ChatOpenAI } from '@langchain/openai'
import { z } from 'zod'
import { Payments } from '@nevermined-io/payments'
import { paymentMiddleware } from '@nevermined-io/payments/express'

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: process.env.NVM_ENVIRONMENT || 'sandbox',
})

const PLAN_ID = process.env.NVM_PLAN_ID!

// Plain tools — no requiresPayment, no config parameter
const searchData = tool(
  (args) => `Results for '${args.query}': ...`,
  { name: 'search_data', description: 'Search for data.', schema: z.object({ query: z.string() }) }
)

const summarizeData = tool(
  (args) => `Summary: ...`,
  { name: 'summarize_data', description: 'Summarize text.', schema: z.object({ text: z.string() }) }
)

// LLM + tools
const llm = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 })
const tools = [searchData, summarizeData]
const llmWithTools = llm.bindTools(tools)
const toolMap = new Map(tools.map((t) => [t.name, t]))

async function runAgent(query: string): Promise<string> {
  const messages: any[] = [new HumanMessage(query)]
  for (let i = 0; i < 10; i++) {
    const ai = await llmWithTools.invoke(messages)
    messages.push(ai)
    if (!ai.tool_calls?.length) return String(ai.content)
    for (const tc of ai.tool_calls) {
      const result = await (toolMap.get(tc.name) as any).invoke(tc.args)
      messages.push(new ToolMessage({ content: result, tool_call_id: tc.id! }))
    }
  }
  return String(messages.at(-1)?.content || 'No response.')
}

// Express app with payment middleware
const app = express()
app.use(express.json())
app.use(paymentMiddleware(payments, {
  'POST /ask': { planId: PLAN_ID, credits: 1 },
}))

app.post('/ask', async (req, res) => {
  const response = await runAgent(req.body.query)
  res.json({ response })
})

app.get('/health', (_req, res) => res.json({ status: 'ok' }))

app.listen(8000, () => console.log('Running on http://localhost:8000'))

Server: LangGraph

Replace the tool-call loop with LangGraph’s create_react_agent:
import { tool } from '@langchain/core/tools'
import { ChatOpenAI } from '@langchain/openai'
import { createReactAgent } from '@langchain/langgraph/prebuilt'
import { z } from 'zod'

// Plain tools — no payment wrappers
const searchData = tool(
  (args) => `Results for '${args.query}': ...`,
  { name: 'search_data', description: 'Search.', schema: z.object({ query: z.string() }) }
)

const agent = createReactAgent({
  llm: new ChatOpenAI({ model: 'gpt-4o-mini' }),
  tools: [searchData, summarizeData],
})

async function runAgent(query: string): Promise<string> {
  const result = await agent.invoke({ messages: [{ role: 'human', content: query }] })
  const messages = result.messages || []
  return messages.at(-1)?.content || 'No response.'
}
The HTTP app, middleware, and route handlers are identical to the LangChain version above.

Client: Full x402 HTTP Flow

import 'dotenv/config'
import { Payments } from '@nevermined-io/payments'

const SERVER_URL = process.env.SERVER_URL || 'http://localhost:8000'
const PLAN_ID = process.env.NVM_PLAN_ID!

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_SUBSCRIBER_API_KEY!,
  environment: process.env.NVM_ENVIRONMENT || 'sandbox',
})

// Step 1: Request without token → 402
const resp402 = await fetch(`${SERVER_URL}/ask`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'AI trends' }),
})
console.log(`Status: ${resp402.status}`) // 402

// Step 2: Decode payment requirements
const pr = JSON.parse(Buffer.from(resp402.headers.get('payment-required')!, 'base64').toString())
console.log(`Plan: ${pr.accepts[0].planId}`)

// Step 3: Acquire x402 token
const token = await payments.x402.getX402AccessToken(PLAN_ID)
const accessToken = token.accessToken

// Step 4: Request with token → 200
const resp200 = await fetch(`${SERVER_URL}/ask`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'payment-signature': accessToken,
  },
  body: JSON.stringify({ query: 'AI trends' }),
})
const body = await resp200.json()
console.log(`Response: ${body.response}`)

// Step 5: Decode settlement receipt
const settlement = JSON.parse(
  Buffer.from(resp200.headers.get('payment-response')!, 'base64').toString()
)
console.log(`Credits charged:   ${settlement.creditsRedeemed}`)
console.log(`Remaining balance: ${settlement.remainingBalance}`)
console.log(`Transaction:       ${settlement.transaction}`)

x402 HTTP Headers

HeaderDirectionDescription
payment-signatureClient → Serverx402 access token
payment-requiredServer → Client (402)Base64-encoded payment requirements
payment-responseServer → Client (200)Base64-encoded settlement receipt
The settlement receipt (payment-response) contains:
FieldDescription
creditsRedeemedNumber of credits charged
remainingBalanceSubscriber’s remaining credit balance
transactionBlockchain transaction hash
networkBlockchain network (CAIP-2 format)
payerSubscriber wallet address

Decorator Configuration

With Agent ID

const myTool = tool(
  requiresPayment(
    (args) => `Result for ${args.query}`,
    {
      payments,
      planId: PLAN_ID,
      credits: 1,
      agentId: process.env.NVM_AGENT_ID,
    }
  ),
  { name: 'my_tool', description: '...', schema: z.object({ query: z.string() }) }
)

Multiple Plans (Python only)

@tool
@requires_payment(
    payments=payments,
    plan_ids=["plan-basic", "plan-premium"],
    credits=1,
)
def my_tool(query: str, config: RunnableConfig) -> str:
    ...

Scheme and Network

const myTool = tool(
  requiresPayment(
    (args) => `Result`,
    { payments, planId: PLAN_ID, credits: 1, network: 'eip155:84532' }
  ),
  { name: 'my_tool', description: '...', schema: z.object({ query: z.string() }) }
)
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.

Complete Examples

Working seller/buyer agents with LangGraph — includes both Python and TypeScript variants:
Each includes:
  • src/server.ts / src/agent.ts — LangGraph createReactAgent with payment-protected tools
  • src/demo.tsrequiresPayment wrapper demo (seller only)
  • src/client.ts — HTTP client with full x402 payment flow

Environment Variables

# Nevermined (required)
NVM_API_KEY=sandbox:your-api-key         # Builder/server API key
NVM_SUBSCRIBER_API_KEY=sandbox:your-key  # Subscriber/client API key
NVM_ENVIRONMENT=sandbox
NVM_PLAN_ID=your-plan-id
NVM_AGENT_ID=your-agent-id              # Optional

# LLM Provider
OPENAI_API_KEY=sk-your-openai-key

Next Steps

Express Middleware (TS)

Deep dive into paymentMiddleware for Express

FastAPI Middleware (Python)

Deep dive into PaymentMiddleware for FastAPI

x402 Protocol

Deep dive into x402 payment flows

Payment Models

Configure credits, subscriptions, and dynamic pricing