Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions api-reference/python/a2a-module.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
icon: "arrows-left-right"
---

This guide explains how to integrate the Nevermined Payments Python SDK with A2A (Agent-to-Agent) protocol servers.

Check warning on line 7 in api-reference/python/a2a-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/a2a-module.mdx#L7

Did you really mean 'Nevermined'?

## Overview

A2A (Agent-to-Agent) is a protocol that enables AI agents to communicate with each other using JSON-RPC. The Nevermined SDK provides A2A integration to:

Check warning on line 11 in api-reference/python/a2a-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/a2a-module.mdx#L11

Did you really mean 'Nevermined'?

- Build A2A servers with payment validation
- Automatically verify x402 tokens on incoming requests
Expand Down Expand Up @@ -68,7 +68,7 @@

### Agent Card Structure

The agent card declares two extensions: the Nevermined payment extension (pricing metadata) and the official a2a-x402 extension (`https://github.com/google-agentic-commerce/a2a-x402/blob/main/spec/v0.2`), which signals support for the standards-compliant in-band x402 v2 flow (see [In-Band x402 v2 Payments](#in-band-x402-v2-payments-standards-flow)). Both ship for one release; `urn:nevermined:payment` is dropped once clients use v0.2 only.

Check warning on line 71 in api-reference/python/a2a-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/a2a-module.mdx#L71

Did you really mean 'Nevermined'?

```json
{
Expand Down Expand Up @@ -304,6 +304,63 @@
result = resp.json()
```

### Using `PaymentsClient` (managed tokens)

`payments.a2a["get_client"]` returns a `PaymentsClient` that mints and
attaches the access token for you:

```python
client = payments.a2a["get_client"](
agent_base_url="http://agent-url/",
agent_id=agent_id,
plan_id=plan_ids[0],
delegation_config=DelegationConfig(delegation_id=delegation_id),
)

await client.send_message(params)
```

The client mints a **v2** token once and caches it for its lifetime β€” a v2
token is a reusable bearer credential. Pass `token_version=3` to request the
single-use, seller/resource-bound token instead:

```python
client = payments.a2a["get_client"](
agent_base_url="http://agent-url/",
agent_id=agent_id,
plan_id=plan_ids[0],
delegation_config=DelegationConfig(delegation_id=delegation_id),
token_version=3,
)
```

On a v3 request the client also **binds** the token, to `agent_base_url` and
`POST`. That default assumes the seller is this SDK's A2A server, which
advertises `str(request.url)`; the backend compares origin + path, so the two
agree. A seller that advertises something else β€” this SDK's FastAPI middleware
advertises a *relative* `request.url.path` β€” would never match and the settle
would fail, so pass the binding explicitly instead:

```python
client = payments.a2a["get_client"](
agent_base_url="http://agent-url/",
agent_id=agent_id,
plan_id=plan_ids[0],
delegation_config=DelegationConfig(delegation_id=delegation_id),
token_version=3,
resource="/a2a/", # whatever the seller advertises in its 402
http_verb="POST",
)
```

A v3 token is consumed by the seller's first settle, so the client mints one
**per paid request** and never caches it β€” replaying one fails with
`BCK.X402.0059`. Which of the two behaviours applies is decided by the token
that came back, not by `token_version`: a backend that predates v3 support
strips the field silently and returns a (cached) v2 token. `clear_token()` is
therefore a no-op on the v3 path. See
[Access Token Versions](/api-reference/python/x402-module#access-token-versions-v2-and-v3).

## Hooks

Add custom logic at request lifecycle points:
Expand Down Expand Up @@ -338,6 +395,7 @@
| -32001 | 402 | Payment validation failed |
| -32001 | 402 | Agent ID missing from card |
| -32001 | 402 | Plan ID missing from card |
| `BCK.X402.0059` | 4xx | v3 access token already spent β€” mint a new one (`AccessTokenAlreadyUsedError`) |

## Next Steps

Expand Down
3 changes: 2 additions & 1 deletion api-reference/python/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
icon: "download"
---

This guide covers how to install the Nevermined Payments Python SDK.

Check warning on line 7 in api-reference/python/installation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/installation.mdx#L7

Did you really mean 'Nevermined'?

## Overview

The Nevermined Payments Python SDK (`payments-py`) is a Python library that provides tools for integrating AI agent monetization and access control into your applications. It supports:

Check warning on line 11 in api-reference/python/installation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/installation.mdx#L11

Did you really mean 'Nevermined'?

- Payment plans (credits-based and time-based)
- AI agent registration and management
Expand All @@ -22,7 +22,7 @@

- **Python 3.10 or higher** - The SDK requires Python 3.10+
- **pip or Poetry** - Package manager for installation
- **Nevermined API Key** - Obtain from the [Nevermined App](https://nevermined.app)

Check warning on line 25 in api-reference/python/installation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/installation.mdx#L25

Did you really mean 'Nevermined'?

## Installation Steps

Expand All @@ -40,7 +40,8 @@

### With Optional Dependencies

For FastAPI/x402 middleware support:
For FastAPI/x402 middleware and MCP server support (installs `fastapi`,
`starlette`, and `uvicorn`):

```bash
# Using pip
Expand Down
111 changes: 111 additions & 0 deletions api-reference/python/langchain-module.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
- **Lambda** β€” `credits=lambda ctx: max(1, len(ctx["result"]) // 100)`.
- **Named function** β€” `credits=my_fn` where `my_fn(ctx) -> int`.

When callable, `ctx` is `{"args": <tool kwargs>, "result": <tool return>}`. Dynamic credits resolve **after** execution so the result is available.

Check warning on line 99 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L99

Did you really mean 'kwargs'?

### Credits semantics

Expand Down Expand Up @@ -127,7 +127,7 @@

| Attribute | Type | Description |
|-----------|------|-------------|
| `payment_required` | `X402PaymentRequired \| None` | The full x402 v2 payment-required payload. `accepts[0]` carries scheme / network / plan_id / agent_id. |

Check warning on line 130 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L130

Did you really mean 'plan_id'?

Check warning on line 130 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L130

Did you really mean 'agent_id'?

For the discovery β†’ acquire β†’ retry flow, see the [LangChain integration guide](/integrate/add-to-your-agent/langchain).

Expand Down Expand Up @@ -167,7 +167,7 @@

## `create_paid_react_agent`

Thin wrapper over [`langgraph.prebuilt.create_react_agent`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent) that constructs the underlying `ToolNode` with `handle_tool_errors=False`. That single change is what lets `PaymentRequiredError` propagate all the way back to `agent.invoke()`'s caller with its `X402PaymentRequired` payload intact β€” the default `ToolNode` behaviour stringifies the exception into a `ToolMessage` for the LLM and **loses the payload**.

Check warning on line 170 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L170

Did you really mean 'stringifies'?

### Signature

Expand Down Expand Up @@ -337,11 +337,11 @@

### Sensitive data in traces

The `payment_token` that the buyer passes via `config["configurable"]["payment_token"]` is captured by LangChain into the parent tool span's metadata, and would normally be inherited by any child span β€” including the `nvm:verify` and `nvm:settlement` spans the decorator emits. The full token grants access to the protected tool until it expires, so the decorator **proactively strips `payment_token` from the parent tool span's metadata** before opening any child span. The full credential never reaches a Nevermined span attribute.

Check warning on line 340 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L340

Did you really mean 'Nevermined'?

For correlation across spans the decorator surfaces an abbreviated `nvm.payment_token` attribute (`eyJ4NDAyVmVyc2lv…bsig`, first 16 chars + ellipsis + last 4) on both `nvm:verify` and `nvm:settlement`. That gives you "which token was this?" without exposing the credential itself.

A real x402 access token is a JWT, which is far longer than 20 chars. If a token of **20 characters or fewer** is passed β€” almost always a misconfiguration (a plan id or opaque handle where the JWT was expected) β€” it is **redacted, not exported**: `nvm.payment_token` shows at most the first 4 chars plus a `…(short)` marker (e.g. `eyJ4…(short)`), and a runtime warning is logged. For a token of 4 chars or fewer, nothing is revealed at all β€” it collapses to just `…(short)`. The full short value never reaches a span attribute, so a misrouted secret cannot leak into a durable trace store even when it is shorter than the abbreviation threshold.

Check warning on line 344 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L344

Did you really mean 'misconfiguration'?

Check warning on line 344 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L344

Did you really mean 'misrouted'?

The active redaction covers the documented LangChain-via-configurable path. If you're surfacing the token through a different channel (custom callbacks, an explicit `add_metadata({"payment_token": ...})`, raw inputs to a tool whose signature contains the token), the decorator can't see those β€” strip them yourself or set `export LANGSMITH_HIDE_INPUTS=true` for blanket coverage.

Expand All @@ -358,15 +358,15 @@

#### Parent metadata is last-writer-wins across tools in one node

`@requires_payment` attaches its `nvm.*` metadata to **two** places: the per-call **child** spans (`nvm:verify` / `nvm:settlement`) and, as a convenience for searchability, the **parent** LangSmith run tree.

Check warning on line 361 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L361

Did you really mean 'searchability'?

The child spans are isolated per call, so they are always correct. The parent copy is **not** namespaced per tool: the bare `nvm.*` keys (`nvm.tx_hash`, `nvm.credits_redeemed`, `nvm.payment_token`, …) are written directly onto the parent run's metadata. When an agent calls **two `@requires_payment` tools within the same LangGraph `ToolNode`** β€” the common pattern, since a single ReAct step can dispatch multiple tool calls into one node β€” both decorators target the **same** parent run tree, and the second `add_metadata` **silently overwrites** the first's `nvm.*` values. The parent therefore reflects only the **last** tool that settled in that node; the earlier tool's parent-level `nvm.*` is lost.

Check warning on line 363 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L363

Did you really mean 'namespaced'?

What this means in practice:

- **Per-call billing fidelity lives on the child spans, not the parent.** For accurate per-tool accounting (which token, which tx hash, how many credits each call redeemed), filter and aggregate on the `nvm:verify` / `nvm:settlement` **child** spans. Each child carries the values for exactly one call.

Check warning on line 367 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L367

Did you really mean 'tx'?
- **Treat parent `nvm.*` as best-effort.** It is convenient for "did this trace touch Nevermined at all?" searches, but do not rely on it for last-writer-sensitive fields when multiple paid tools can run in one node.

Check warning on line 368 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L368

Did you really mean 'Nevermined'?
- This is a **last-writer-wins** behaviour, not a correctness bug in settlement β€” every call still verifies and settles independently and correctly. Only the parent's *denormalized copy* of the metadata is affected.

Check warning on line 369 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L369

Did you really mean 'denormalized'?

This matches the cross-SDK **observability spans v1** contract (the SDK-neutral span spec maintained in `nvm-monorepo`, which both `payments-py` and `@nevermined-io/payments` emit against): child spans are authoritative per tool; parent `nvm.*` is best-effort / last-writer-wins.

Expand All @@ -389,8 +389,119 @@

Span emission failures are caught internally β€” observability is best-effort and will not interfere with the payment flow.

## Deep Agents

[Deep Agents](https://docs.langchain.com/oss/python/deepagents/overview) is
LangChain's agent *harness*: `create_deep_agent()` returns a compiled LangGraph
graph with planning, a filesystem, and subagent delegation built in.

**`requires_payment` needs no changes to work with it.** The decorator reads the
token from `config["configurable"]["payment_token"]`, and LangGraph copies
`configurable` down into subagent tool calls, so a paid tool keeps working when
it sits behind a `task()` delegation:

```
main agent --task()--> research-sub --> market_research [PAID]
^ |
+--------- x402 token supplied here ----------+
config.configurable.payment_token
```

That property is what makes the harness usable for monetized capabilities at
all: a deep agent's premise is that the supervisor hands work to subagents, so

Check warning on line 411 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L411

Did you really mean 'subagents'?
if payment context did not survive the hop, every paid tool would have to sit on
the main agent.

```python
from deepagents import create_deep_agent
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool

from payments_py.x402.langchain import PaymentRequiredError, requires_payment


@requires_payment(payments=payments, plan_id=PLAN_ID, credits=5)
def _market_research_paid(topic: str, config: RunnableConfig) -> str:
return run_analyst(topic)


@tool
def market_research(topic: str, config: RunnableConfig) -> str:
"""Paid market research on a topic."""
try:
# `config` must be forwarded explicitly β€” the decorator reads the
# token from it.
return _market_research_paid(topic, config=config)
except PaymentRequiredError:
return "PAYMENT_REQUIRED: authorize and ask again."


# The paid tool is given ONLY to the subagent, so every paid call crosses
# a delegation boundary.
graph = create_deep_agent(
model="openai:gpt-4o-mini",
tools=[],
subagents=[{
"name": "research-sub",
"description": "Performs paid market research.",
"system_prompt": "Call market_research once and return its output verbatim.",
"tools": [market_research],
}],
system_prompt="Delegate research requests to research-sub.",
)
```

The buyer side is unchanged β€” the token goes on the run, and the buyer does not
need to know the agent's internal topology:

```python
graph.invoke(
{"messages": [{"role": "user", "content": "Research the EV market"}]},
config={"configurable": {"payment_token": access_token}},
)
```

### Two harness behaviours to design around

**A deep agent can bill several times per user turn.** The supervisor, not you,
decides how many subagent calls a request warrants, so one user message may
settle credits more than once. Cap it explicitly rather than trusting the model
to be frugal β€” count paid calls per run (key on
`config["configurable"]["thread_id"]` or `run_id`) and return a plain refusal
once the cap is hit. Refund the reservation when a call raises
`PaymentRequiredError`, so a user who authorizes mid-run still gets what they
paid for.

**Two LLM layers can paraphrase the tool's output.** The subagent relays to the
supervisor, which relays to the user; neither is guaranteed to pass text through
verbatim, and a capable supervisor may even answer a paid question from its own
knowledge instead of delegating β€” silently giving the capability away. Forbid
that explicitly in both system prompts, and treat the tool's return value, not
the chat reply, as the source of truth.

### Version note

`deepagents` requires the LangChain v1 stack (`langchain>=1.3.18`,
`langchain-core>=1.6.1`). If your project pins an older `langchain-core`, give
the deep agent its own virtualenv.

Check warning on line 486 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L486

Did you really mean 'virtualenv'?

Compatibility is pinned by `tests/unit/x402/test_deepagents_compat.py`, which
drives a real deep agent through a scripted fake model (no network, no LLM) and
asserts the token reaches a subagent's tool. They run in CI under the dedicated

Check warning on line 490 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L490

Did you really mean 'subagent's'?
`deepagents_compat` job β€” separate from the main test job, which pins the
Python 3.10 floor that `deepagents` cannot install on. To reproduce locally:

```bash
python3.11 -m venv .venv-deepagents
.venv-deepagents/bin/pip install -e ".[langchain,langsmith]"
.venv-deepagents/bin/pip install deepagents pytest
.venv-deepagents/bin/pytest tests/unit/x402/test_deepagents_compat.py
```

## Related

- [LangChain integration guide](/integrate/add-to-your-agent/langchain) β€” conceptual walk-through, the two integration approaches (decorator vs. HTTP middleware), and the TypeScript variant.
- [x402 Protocol](/api-reference/python/x402-module) β€” token generation, delegation config, scheme resolution.
- [`tutorials/langchain-paid-agent-py`](https://github.com/nevermined-io/tutorials/tree/main/langchain-paid-agent-py) β€” the minimal end-to-end demo.
- [`tutorials/langchain-research-agent-py`](https://github.com/nevermined-io/tutorials/tree/main/langchain-research-agent-py) β€” freemium in-tool gating on `create_react_agent`.

Check warning on line 506 in api-reference/python/langchain-module.mdx

View check run for this annotation

Mintlify / Mintlify Validation (neverminedag) - vale-spellcheck

api-reference/python/langchain-module.mdx#L506

Did you really mean 'freemium'?
- [`tutorials/langchain-deep-agent-py`](https://github.com/nevermined-io/tutorials/tree/main/langchain-deep-agent-py) β€” the same pattern on the Deep Agents harness, with the paid tool inside a subagent.
Loading